From 488cc5e73be3c0aba2caf4df6ae38225c6a3d641 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 9 Mar 2020 00:25:00 +0100 Subject: [PATCH 01/70] First design iteration of device verification dialogs --- .../DeviceVerification.qml | 392 ++++++++++++++++++ .../DeviceVerificationTest.qml | 13 + .../qml/device-verification/sas-emoji.json | 66 +++ 3 files changed, 471 insertions(+) create mode 100644 resources/qml/device-verification/DeviceVerification.qml create mode 100644 resources/qml/device-verification/DeviceVerificationTest.qml create mode 100644 resources/qml/device-verification/sas-emoji.json diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml new file mode 100644 index 0000000..c32e341 --- /dev/null +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -0,0 +1,392 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Window 2.2 +import QtQuick.Layouts 1.10 + +Window { + title: stack.currentItem.title + id: dialog + + flags: Qt.Dialog + + + height: stack.implicitHeight + width: stack.implicitWidth + StackView { + id: stack + initialItem: newVerificationRequest + implicitWidth: currentItem.implicitWidth + implicitHeight: currentItem.implicitHeight + } + + onClosing: stack.replace(newVerificationRequest) + + Component { + id: newVerificationRequest + Pane { + property string title: "Device Verification Request" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: "A new device was added." + + verticalAlignment: Text.AlignVCenter + } + + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: "The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device." + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: "Cancel" + onClicked: dialog.close() + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "Start verification" + onClicked: stack.replace(awaitingVerificationRequestAccept) + } + } + } + } + } + + Component { + id: awaitingVerificationRequestAccept + Pane { + property string title: "Waiting for other party" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: "Waiting for other side to accept the verification request." + + verticalAlignment: Text.AlignVCenter + } + + BusyIndicator { + Layout.alignment: Qt.AlignHCenter + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: "Cancel" + onClicked: dialog.close() + } + Item { + Layout.fillWidth: true + } + } + Timer { + // temporary, until it is bound to a backend + interval: 5000; running: true; + onTriggered: if (Math.random() > 0.5) stack.replace(emojiVerification); else stack.replace(digitVerification); + } + } + } + } + + Component { + id: digitVerification + Pane { + property string title: "Verification Code" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: "Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!" + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + Text { + font.pixelSize: Qt.application.font.pixelSize * 2 + text: "1234" + } + Text { + font.pixelSize: Qt.application.font.pixelSize * 2 + text: "1234" + } + Text { + font.pixelSize: Qt.application.font.pixelSize * 2 + text: "1234" + } + } + + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: "They do not match!" + onClicked: dialog.close() + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "They match." + onClicked: stack.replace(awaitingVerificationConfirmation) + } + } + } + } + } + + Component { + id: emojiVerification + Pane { + property string title: "Verification Code" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: "Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!" + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + + id: emojis + + property var mapping: [ + {"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"}, + {"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"}, + {"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"}, + {"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"}, + {"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"}, + {"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"}, + {"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"}, + {"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"}, + {"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"}, + {"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"}, + {"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"}, + {"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"}, + {"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"}, + {"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"}, + {"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"}, + {"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"}, + {"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"}, + {"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"}, + {"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"}, + {"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"}, + {"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"}, + {"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"}, + {"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"}, + {"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"}, + {"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"}, + {"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"}, + {"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"}, + {"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"}, + {"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"}, + {"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"}, + {"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"}, + {"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"}, + {"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"}, + {"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"}, + {"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"}, + {"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"}, + {"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"}, + {"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"}, + {"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"}, + {"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"}, + {"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"}, + {"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"}, + {"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"}, + {"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"}, + {"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"}, + {"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"}, + {"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"}, + {"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"}, + {"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"}, + {"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"}, + {"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"}, + {"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"}, + {"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"}, + {"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"}, + {"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"}, + {"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"}, + {"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"}, + {"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"}, + {"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"}, + {"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"}, + {"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"}, + {"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"}, + {"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"}, + {"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"} + ] + + Repeater { + id: repeater + model: 7 + delegate: Rectangle { + color: "red" + implicitHeight: Qt.application.font.pixelSize * 8 + implicitWidth: col.width + ColumnLayout { + id: col + anchors.bottom: parent.bottom + property var emoji: emojis.mapping[Math.floor(Math.random()*64)] + Text { + height: font.pixelSize * 2 + Layout.alignment: Qt.AlignHCenter + text: col.emoji.emoji + font.pixelSize: Qt.application.font.pixelSize * 4 + } + Text { + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + text: col.emoji.description + } + } + } + } + } + + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: "They do not match!" + onClicked: dialog.close() + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "They match." + onClicked: stack.replace(awaitingVerificationConfirmation) + } + } + } + } + } + + Component { + id: awaitingVerificationConfirmation + Pane { + property string title: "Awaiting Confirmation" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: "Waiting for other side to complete verification." + + verticalAlignment: Text.AlignVCenter + } + + BusyIndicator { + Layout.alignment: Qt.AlignHCenter + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: "Cancel" + onClicked: dialog.close() + } + Item { + Layout.fillWidth: true + } + } + Timer { + // temporary, until it is bound to a backend + interval: 5000; running: true; + onTriggered: Math.random() > 0.5 ? stack.replace(verificationSuccess) : stack.replace(partnerAborted) + } + } + } + } + + Component { + id: verificationSuccess + Pane { + property string title: "Successful Verification" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: "Verification successful! Both sides verified their devices!" + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "Close" + onClicked: dialog.close() + } + } + } + } + } + + Component { + id: partnerAborted + Pane { + property string title: "Verification aborted!" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: "Verification canceled by the other party!" + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "Close" + onClicked: dialog.close() + } + } + } + } + } +} diff --git a/resources/qml/device-verification/DeviceVerificationTest.qml b/resources/qml/device-verification/DeviceVerificationTest.qml new file mode 100644 index 0000000..6682e7e --- /dev/null +++ b/resources/qml/device-verification/DeviceVerificationTest.qml @@ -0,0 +1,13 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +Item { + DeviceVerification { + id: deviceVerification + } + + Button { + text: "Test DeviceVerification" + onClicked: deviceVerification.show() + } +} diff --git a/resources/qml/device-verification/sas-emoji.json b/resources/qml/device-verification/sas-emoji.json new file mode 100644 index 0000000..060fbd4 --- /dev/null +++ b/resources/qml/device-verification/sas-emoji.json @@ -0,0 +1,66 @@ +[ + {"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"}, + {"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"}, + {"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"}, + {"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"}, + {"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"}, + {"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"}, + {"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"}, + {"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"}, + {"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"}, + {"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"}, + {"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"}, + {"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"}, + {"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"}, + {"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"}, + {"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"}, + {"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"}, + {"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"}, + {"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"}, + {"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"}, + {"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"}, + {"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"}, + {"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"}, + {"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"}, + {"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"}, + {"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"}, + {"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"}, + {"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"}, + {"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"}, + {"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"}, + {"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"}, + {"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"}, + {"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"}, + {"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"}, + {"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"}, + {"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"}, + {"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"}, + {"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"}, + {"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"}, + {"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"}, + {"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"}, + {"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"}, + {"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"}, + {"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"}, + {"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"}, + {"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"}, + {"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"}, + {"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"}, + {"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"}, + {"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"}, + {"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"}, + {"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"}, + {"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"}, + {"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"}, + {"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"}, + {"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"}, + {"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"}, + {"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"}, + {"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"}, + {"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"}, + {"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"}, + {"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"}, + {"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"}, + {"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"}, + {"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"} +] From 2088053d26fc124058fafb434d41b7c9516f0da0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 13 Mar 2020 21:05:18 +0100 Subject: [PATCH 02/70] Add DeviceVerificationFlow dummy and verification test button --- CMakeLists.txt | 2 + resources/qml/TimelineView.qml | 19 +++++ .../DeviceVerification.qml | 77 ++++++++++++++----- .../DeviceVerificationTest.qml | 13 ---- resources/res.qrc | 1 + src/DeviceVerificationFlow.cpp | 36 +++++++++ src/DeviceVerificationFlow.h | 38 +++++++++ src/timeline/TimelineViewManager.cpp | 7 ++ src/timeline/TimelineViewManager.h | 3 + 9 files changed, 163 insertions(+), 33 deletions(-) delete mode 100644 resources/qml/device-verification/DeviceVerificationTest.qml create mode 100644 src/DeviceVerificationFlow.cpp create mode 100644 src/DeviceVerificationFlow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6926104..69a46bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,6 +284,7 @@ set(SRC_FILES src/ColorImageProvider.cpp src/CommunitiesList.cpp src/CommunitiesListItem.cpp + src/DeviceVerificationFlow.cpp src/EventAccessors.cpp src/InviteeItem.cpp src/Logging.cpp @@ -488,6 +489,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ChatPage.h src/CommunitiesList.h src/CommunitiesListItem.h + src/DeviceVerificationFlow.h src/InviteeItem.h src/LoginPage.h src/MainWindow.h diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 8a5612d..dd35473 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -9,6 +9,7 @@ import im.nheko.EmojiModel 1.0 import "./delegates" import "./emoji" +import "./device-verification" Page { id: timelineRoot @@ -98,6 +99,24 @@ Page { anchors.fill: parent color: colors.window + Component { + id: deviceVerificationDialog + DeviceVerification {} + } + Connections { + target: timelineManager + onDeviceVerificationRequest: { + var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: deviceVerificationFlow}); + dialog.show(); + } + } + + Button { + text: "test device verification" + onClicked: timelineManager.startDummyVerification() + z: 5 + } + Label { visible: !timelineManager.timeline && !timelineManager.isInitialSync anchors.centerIn: parent diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index c32e341..d875231 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -3,12 +3,15 @@ import QtQuick.Controls 2.10 import QtQuick.Window 2.2 import QtQuick.Layouts 1.10 -Window { +import im.nheko 1.0 + +ApplicationWindow { title: stack.currentItem.title id: dialog flags: Qt.Dialog + palette: colors height: stack.implicitHeight width: stack.implicitWidth @@ -21,6 +24,19 @@ Window { onClosing: stack.replace(newVerificationRequest) + property var flow + Connections { + target: flow + onVerificationCanceled: stack.replace(partnerAborted) + onTimedout: stack.replace(timedout) + onDeviceVerified: stack.replace(verificationSuccess) + + onVerificationRequestAccepted: switch(method) { + case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break; + case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break; + } + } + Component { id: newVerificationRequest Pane { @@ -51,7 +67,7 @@ Window { Button { Layout.alignment: Qt.AlignLeft text: "Cancel" - onClicked: dialog.close() + onClicked: { dialog.close(); flow.cancelVerification(); } } Item { Layout.fillWidth: true @@ -59,7 +75,7 @@ Window { Button { Layout.alignment: Qt.AlignRight text: "Start verification" - onClicked: stack.replace(awaitingVerificationRequestAccept) + onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.acceptVerificationRequest(); } } } } @@ -90,17 +106,12 @@ Window { Button { Layout.alignment: Qt.AlignLeft text: "Cancel" - onClicked: dialog.close() + onClicked: { dialog.close(); flow.cancelVerification(); } } Item { Layout.fillWidth: true } } - Timer { - // temporary, until it is bound to a backend - interval: 5000; running: true; - onTriggered: if (Math.random() > 0.5) stack.replace(emojiVerification); else stack.replace(digitVerification); - } } } } @@ -141,7 +152,7 @@ Window { Button { Layout.alignment: Qt.AlignLeft text: "They do not match!" - onClicked: dialog.close() + onClicked: { dialog.close(); flow.cancelVerification(); } } Item { Layout.fillWidth: true @@ -149,7 +160,7 @@ Window { Button { Layout.alignment: Qt.AlignRight text: "They match." - onClicked: stack.replace(awaitingVerificationConfirmation) + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.acceptDevice(); } } } } @@ -248,7 +259,7 @@ Window { id: repeater model: 7 delegate: Rectangle { - color: "red" + color: "transparent" implicitHeight: Qt.application.font.pixelSize * 8 implicitWidth: col.width ColumnLayout { @@ -274,7 +285,7 @@ Window { Button { Layout.alignment: Qt.AlignLeft text: "They do not match!" - onClicked: dialog.close() + onClicked: { dialog.close(); flow.cancelVerification(); } } Item { Layout.fillWidth: true @@ -282,7 +293,7 @@ Window { Button { Layout.alignment: Qt.AlignRight text: "They match." - onClicked: stack.replace(awaitingVerificationConfirmation) + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.acceptDevice(); } } } } @@ -313,17 +324,12 @@ Window { Button { Layout.alignment: Qt.AlignLeft text: "Cancel" - onClicked: dialog.close() + onClicked: { dialog.close(); flow.cancelVerification(); } } Item { Layout.fillWidth: true } } - Timer { - // temporary, until it is bound to a backend - interval: 5000; running: true; - onTriggered: Math.random() > 0.5 ? stack.replace(verificationSuccess) : stack.replace(partnerAborted) - } } } } @@ -389,4 +395,35 @@ Window { } } } + + Component { + id: timedout + Pane { + property string title: "Verification timed out" + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: "Device verification timed out." + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "Close" + onClicked: dialog.close() + } + } + } + } + } } diff --git a/resources/qml/device-verification/DeviceVerificationTest.qml b/resources/qml/device-verification/DeviceVerificationTest.qml deleted file mode 100644 index 6682e7e..0000000 --- a/resources/qml/device-verification/DeviceVerificationTest.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 - -Item { - DeviceVerification { - id: deviceVerification - } - - Button { - text: "Test DeviceVerification" - onClicked: deviceVerification.show() - } -} diff --git a/resources/res.qrc b/resources/res.qrc index 439ed97..ec086b3 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -135,5 +135,6 @@ qml/delegates/Pill.qml qml/delegates/Placeholder.qml qml/delegates/Reply.qml + qml/device-verification/DeviceVerification.qml diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp new file mode 100644 index 0000000..69d6ab9 --- /dev/null +++ b/src/DeviceVerificationFlow.cpp @@ -0,0 +1,36 @@ +#include "DeviceVerificationFlow.h" + +#include + +static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes + +DeviceVerificationFlow::DeviceVerificationFlow(QObject *) +{ + timeout = new QTimer(this); + timeout->setSingleShot(true); + connect(timeout, &QTimer::timeout, this, [this]() { + emit timedout(); + this->deleteLater(); + }); + timeout->start(TIMEOUT); +} + +//! accepts a verification and starts the verification flow +void +DeviceVerificationFlow::acceptVerificationRequest() +{ + emit verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); +} +//! cancels a verification flow +void +DeviceVerificationFlow::cancelVerification() +{ + this->deleteLater(); +} +//! Completes the verification flow +void +DeviceVerificationFlow::acceptDevice() +{ + emit deviceVerified(); + this->deleteLater(); +} diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h new file mode 100644 index 0000000..038f1e1 --- /dev/null +++ b/src/DeviceVerificationFlow.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +class QTimer; + +class DeviceVerificationFlow : public QObject +{ + Q_OBJECT + // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + +public: + enum Method + { + Decimal, + Emoji + }; + Q_ENUM(Method) + + DeviceVerificationFlow(QObject *parent = nullptr); + +public slots: + //! accepts a verification and starts the verification flow + void acceptVerificationRequest(); + //! cancels a verification flow + void cancelVerification(); + //! Completes the verification flow + void acceptDevice(); + +signals: + void verificationRequestAccepted(Method method); + void deviceVerified(); + void timedout(); + void verificationCanceled(); + +private: + QTimer *timeout = nullptr; +}; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 975dd5f..8447619 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -85,6 +85,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin "Can't instantiate enum!"); qmlRegisterType("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); + qmlRegisterType("im.nheko", 1, 0, "DeviceVerificationFlow"); qRegisterMetaType(); qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel"); qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel"); @@ -460,3 +461,9 @@ TimelineViewManager::queueVideoMessage(const QString &roomid, model->sendMessage(video); } + +void +TimelineViewManager::startDummyVerification() +{ + emit deviceVerificationRequest(new DeviceVerificationFlow(this)); +} diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 6310691..583a9e4 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -10,6 +10,7 @@ #include #include "Cache.h" +#include "DeviceVerificationFlow.h" #include "Logging.h" #include "TimelineModel.h" #include "Utils.h" @@ -43,6 +44,7 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); + Q_INVOKABLE void startDummyVerification(); Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; @@ -54,6 +56,7 @@ signals: void initialSyncChanged(bool isInitialSync); void replyingEventChanged(QString replyingEvent); void replyClosed(); + void deviceVerificationRequest(DeviceVerificationFlow *deviceVerificationFlow); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); From 480c4bc8f5f9bff50c832d10861b9fabce03464d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Mar 2020 11:17:10 +0100 Subject: [PATCH 03/70] Set proper emoji font for device verification --- resources/qml/device-verification/DeviceVerification.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index d875231..d0f4446 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -2,6 +2,7 @@ import QtQuick 2.3 import QtQuick.Controls 2.10 import QtQuick.Window 2.2 import QtQuick.Layouts 1.10 +import Qt.labs.settings 1.0 import im.nheko 1.0 @@ -13,6 +14,12 @@ ApplicationWindow { palette: colors + Settings { + id: settings + category: "user" + property bool emoji_font_family: true + } + height: stack.implicitHeight width: stack.implicitWidth StackView { @@ -271,6 +278,7 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter text: col.emoji.emoji font.pixelSize: Qt.application.font.pixelSize * 4 + font.family: settings.emoji_font_family } Text { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom From fed0463e57307a3ecaf01dd7d2b54f4d42044e15 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 20 Mar 2020 21:07:26 +0100 Subject: [PATCH 04/70] Make emojis a bit smaller --- .../DeviceVerification.qml | 6 ++--- .../qml/device-verification/EmojiElement.qml | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 resources/qml/device-verification/EmojiElement.qml diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index d0f4446..f8fe2bd 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -267,17 +267,17 @@ ApplicationWindow { model: 7 delegate: Rectangle { color: "transparent" - implicitHeight: Qt.application.font.pixelSize * 8 + implicitHeight: Qt.application.font.pixelSize * 3 implicitWidth: col.width ColumnLayout { id: col anchors.bottom: parent.bottom property var emoji: emojis.mapping[Math.floor(Math.random()*64)] Text { - height: font.pixelSize * 2 + //height: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter text: col.emoji.emoji - font.pixelSize: Qt.application.font.pixelSize * 4 + font.pixelSize: Qt.application.font.pixelSize * 2 font.family: settings.emoji_font_family } Text { diff --git a/resources/qml/device-verification/EmojiElement.qml b/resources/qml/device-verification/EmojiElement.qml new file mode 100644 index 0000000..fa207b9 --- /dev/null +++ b/resources/qml/device-verification/EmojiElement.qml @@ -0,0 +1,25 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.10 + +Rectangle { + color: "red" + implicitHeight: Qt.application.font.pixelSize * 4 + implicitWidth: col.width + height: Qt.application.font.pixelSize * 4 + width: col.width + ColumnLayout { + id: col + anchors.bottom: parent.bottom + property var emoji: emojis.mapping[Math.floor(Math.random()*64)] + Text { + height: font.pixelSize * 2 + Layout.alignment: Qt.AlignHCenter + text: col.emoji.emoji + font.pixelSize: Qt.application.font.pixelSize * 2 + } + Text { + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + text: col.emoji.description + } + } +} From b1362ca69fe2c028546b1e4bab5506395f431e07 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 31 Mar 2020 00:47:56 +0200 Subject: [PATCH 05/70] Use label in device verification dialogs (for proper theming) --- .../DeviceVerification.qml | 28 +++++++++---------- .../qml/device-verification/EmojiElement.qml | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index f8fe2bd..19e0c7d 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -50,7 +50,7 @@ ApplicationWindow { property string title: "Device Verification Request" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -60,7 +60,7 @@ ApplicationWindow { verticalAlignment: Text.AlignVCenter } - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -95,7 +95,7 @@ ApplicationWindow { property string title: "Waiting for other party" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -129,7 +129,7 @@ ApplicationWindow { property string title: "Verification Code" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -141,15 +141,15 @@ ApplicationWindow { RowLayout { Layout.alignment: Qt.AlignHCenter - Text { + Label { font.pixelSize: Qt.application.font.pixelSize * 2 text: "1234" } - Text { + Label { font.pixelSize: Qt.application.font.pixelSize * 2 text: "1234" } - Text { + Label { font.pixelSize: Qt.application.font.pixelSize * 2 text: "1234" } @@ -180,7 +180,7 @@ ApplicationWindow { property string title: "Verification Code" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -273,14 +273,14 @@ ApplicationWindow { id: col anchors.bottom: parent.bottom property var emoji: emojis.mapping[Math.floor(Math.random()*64)] - Text { + Label { //height: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter text: col.emoji.emoji font.pixelSize: Qt.application.font.pixelSize * 2 font.family: settings.emoji_font_family } - Text { + Label { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: col.emoji.description } @@ -314,7 +314,7 @@ ApplicationWindow { property string title: "Awaiting Confirmation" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -348,7 +348,7 @@ ApplicationWindow { property string title: "Successful Verification" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -379,7 +379,7 @@ ApplicationWindow { property string title: "Verification aborted!" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true @@ -410,7 +410,7 @@ ApplicationWindow { property string title: "Verification timed out" ColumnLayout { spacing: 16 - Text { + Label { Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true diff --git a/resources/qml/device-verification/EmojiElement.qml b/resources/qml/device-verification/EmojiElement.qml index fa207b9..22f9e41 100644 --- a/resources/qml/device-verification/EmojiElement.qml +++ b/resources/qml/device-verification/EmojiElement.qml @@ -11,13 +11,13 @@ Rectangle { id: col anchors.bottom: parent.bottom property var emoji: emojis.mapping[Math.floor(Math.random()*64)] - Text { + Label { height: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter text: col.emoji.emoji font.pixelSize: Qt.application.font.pixelSize * 2 } - Text { + Label { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: col.emoji.description } From 707248fea39eafdfc4ce6b69987ab144ab781110 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 13 Mar 2020 21:05:18 +0100 Subject: [PATCH 06/70] Add DeviceVerificationFlow dummy and verification test button --- resources/qml/device-verification/DeviceVerification.qml | 8 ++++++++ src/timeline/TimelineViewManager.cpp | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 19e0c7d..2c9486a 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -267,7 +267,11 @@ ApplicationWindow { model: 7 delegate: Rectangle { color: "transparent" +<<<<<<< HEAD implicitHeight: Qt.application.font.pixelSize * 3 +======= + implicitHeight: Qt.application.font.pixelSize * 8 +>>>>>>> Add DeviceVerificationFlow dummy and verification test button implicitWidth: col.width ColumnLayout { id: col @@ -410,7 +414,11 @@ ApplicationWindow { property string title: "Verification timed out" ColumnLayout { spacing: 16 +<<<<<<< HEAD Label { +======= + Text { +>>>>>>> Add DeviceVerificationFlow dummy and verification test button Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 8447619..afd1acb 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -467,3 +467,9 @@ TimelineViewManager::startDummyVerification() { emit deviceVerificationRequest(new DeviceVerificationFlow(this)); } + +void +TimelineViewManager::startDummyVerification() +{ + emit deviceVerificationRequest(new DeviceVerificationFlow(this)); +} From 64f204d984d4c4f520f8c5de613e17b01dd9a317 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Sun, 17 May 2020 19:04:47 +0530 Subject: [PATCH 07/70] Rewrite UserProfile in qml --- CMakeLists.txt | 2 + resources/qml/TimelineView.qml | 13 +++++- resources/qml/UserProfile.qml | 85 ++++++++++++++++++++++++++++++++++ resources/res.qrc | 1 + src/Olm.cpp | 16 +++++++ src/ui/UserProfile.cpp | 58 +++++++++++++++++++++++ src/ui/UserProfile.h | 29 ++++++++++++ 7 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 resources/qml/UserProfile.qml create mode 100644 src/ui/UserProfile.cpp create mode 100644 src/ui/UserProfile.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 69a46bd..e170495 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,6 +276,7 @@ set(SRC_FILES src/ui/ToggleButton.cpp src/ui/Theme.cpp src/ui/ThemeManager.cpp + src/ui/UserProfile.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp @@ -480,6 +481,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/ToggleButton.h src/ui/Theme.h src/ui/ThemeManager.h + src/ui/UserProfile.h src/notifications/Manager.h diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index dd35473..ed403aa 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -286,7 +286,10 @@ Page { MouseArea { anchors.fill: parent - onClicked: chat.model.openUserProfile(modelData.userId) + onClicked: { + userProfile.user_data = modelData + userProfile.show() + } cursorShape: Qt.PointingHandCursor propagateComposedEvents: true } @@ -300,7 +303,10 @@ Page { MouseArea { anchors.fill: parent - onClicked: chat.model.openUserProfile(section.split(" ")[0]) + onClicked: { + userProfile.user_data = modelData + userProfile.show() + } cursorShape: Qt.PointingHandCursor propagateComposedEvents: true } @@ -314,6 +320,9 @@ Page { width: chat.delegateMaxWidth - parent.spacing*2 - userName.implicitWidth - avatarSize font.italic: true } + UserProfile{ + id: userProfile + } } } } diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml new file mode 100644 index 0000000..f019ee2 --- /dev/null +++ b/resources/qml/UserProfile.qml @@ -0,0 +1,85 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.3 + +ApplicationWindow{ + property var user_data + property var colors: currentActivePalette + + id:userProfileDialog + height: 500 + width: 500 + modality:Qt.WindowModal + Layout.alignment: Qt.AlignHCenter + palette: colors + + onAfterRendering: { + userProfileAvatar.url = chat.model.avatarUrl(user_data.userId).replace("mxc://", "image://MxcImage/") + userProfileName.text = user_data.userName + matrixUserID.text = user_data.userId + console.log("this is happening"); + } + + background: Item{ + id: userProfileItem + width: userProfileDialog.width + height: userProfileDialog.height + anchors.margins: { + top:20 + } + + ColumnLayout{ + anchors.fill: userProfileItem + width: userProfileDialog.width + spacing: 10 + + Avatar{ + id: userProfileAvatar + height: 130 + width: 130 + displayName: modelData.userName + Layout.alignment: Qt.AlignHCenter + } + + Label{ + id: userProfileName + fontSizeMode: Text.HorizontalFit + Layout.alignment: Qt.AlignHCenter + } + + Label{ + id: matrixUserID + fontSizeMode: Text.HorizontalFit + Layout.alignment: Qt.AlignHCenter + } + + ScrollView { + implicitHeight: userProfileDialog.height/2+20 + implicitWidth: userProfileDialog.width-20 + clip: true + Layout.alignment: Qt.AlignHCenter + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + + Label { + text: "ABC" + font.pixelSize: 700 + } + } + + Button{ + text:"OK" + onClicked: userProfileDialog.close() + anchors.margins: { + right:10 + bottom:10 + } + + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + } + } + + Item { Layout.fillHeight: true } + } +} diff --git a/resources/res.qrc b/resources/res.qrc index ec086b3..cb724dd 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -126,6 +126,7 @@ qml/TimelineRow.qml qml/emoji/EmojiButton.qml qml/emoji/EmojiPicker.qml + qml/UserProfile.qml qml/delegates/MessageDelegate.qml qml/delegates/TextMessage.qml qml/delegates/NoticeMessage.qml diff --git a/src/Olm.cpp b/src/Olm.cpp index 994a3a6..494bc20 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -52,6 +52,11 @@ handle_to_device_messages(const std::vectorwarn("validation error for olm message: {} {}", e.what(), j_msg.dump(2)); + + nhlog::crypto()->warn("validation error for olm message: {} {}", + e.what(), + j_msg.dump(2)); + } } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) { @@ -368,6 +373,10 @@ handle_key_request_message(const mtx::events::DeviceEventwarn("requested session not found in room: {}", req.content.room_id); + + nhlog::crypto()->warn("requested session not found in room: {}", + req.content.room_id); + return; } @@ -390,6 +399,13 @@ handle_key_request_message(const mtx::events::DeviceEventdebug("ignoring all key requests for room {}", + req.content.room_id); + + nhlog::crypto()->debug("ignoring all key requests for room {}", + req.content.room_id); + nhlog::crypto()->debug("ignoring all key requests for room {}", req.content.room_id); return; diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp new file mode 100644 index 0000000..ac35f1d --- /dev/null +++ b/src/ui/UserProfile.cpp @@ -0,0 +1,58 @@ +#include "UserProfile.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "Utils.h" + +UserProfile::UserProfile(QObject *parent) + : QObject(parent) +{} + +QMap +UserProfile::getDeviceList() +{ + return this->deviceList; +} + +void +UserProfile::fetchDeviceList(const QString &userId) +{ + auto localUser = utils::localUser(); + mtx::requests::QueryKeys req; + req.device_keys[userId.toStdString()] = {}; + + http::client()->query_keys( + req, + [user_id = userId.toStdString()](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + if (res.device_keys.empty() || + (res.device_keys.find(user_id) == res.device_keys.end())) { + nhlog::net()->warn("no devices retrieved {}", user_id); + return; + } + + auto devices = res.device_keys.at(user_id); + + std::vector deviceInfo; + for (const auto &d : devices) { + auto device = d.second; + + // TODO: Verify signatures and ignore those that don't pass. + deviceInfo.emplace_back(DeviceInfo{ + QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name)}); + } + + std::sort(deviceInfo.begin(), + deviceInfo.end(), + [](const DeviceInfo &a, const DeviceInfo &b) { + return a.device_id > b.device_id; + }); + }); +} diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h new file mode 100644 index 0000000..d003e6c --- /dev/null +++ b/src/ui/UserProfile.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +struct DeviceInfo +{ + QString device_id; + QString display_name; +}; + +class UserProfile : public QObject +{ + Q_OBJECT + Q_PROPERTY(QMap deviceList READ getDeviceList NOTIFY DeviceListUpdated) + +public: + explicit UserProfile(QObject *parent = 0); + QMap getDeviceList(); + + Q_INVOKABLE void fetchDeviceList(const QString &userID); + +signals: + void DeviceListUpdated(); + +private: + QMap deviceList; +}; \ No newline at end of file From a54a973ad6be4a1e71be6d8f993600fb02601574 Mon Sep 17 00:00:00 2001 From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com> Date: Fri, 22 May 2020 11:17:02 +0530 Subject: [PATCH 08/70] Adding DeviceList for userprofile --- resources/qml/UserProfile.qml | 18 +++++++- .../DeviceVerification.qml | 8 ---- src/timeline/TimelineViewManager.cpp | 9 ++-- src/ui/UserProfile.cpp | 44 ++++++++++++++----- src/ui/UserProfile.h | 32 +++++++++++--- 5 files changed, 79 insertions(+), 32 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index f019ee2..ae91abc 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -3,6 +3,8 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 +import im.nheko 1.0 + ApplicationWindow{ property var user_data property var colors: currentActivePalette @@ -18,7 +20,21 @@ ApplicationWindow{ userProfileAvatar.url = chat.model.avatarUrl(user_data.userId).replace("mxc://", "image://MxcImage/") userProfileName.text = user_data.userName matrixUserID.text = user_data.userId - console.log("this is happening"); + userProfile.userId = user_data.userId + log_devices() + } + + function log_devices() + { + console.log(userProfile.deviceList); + userProfile.deviceList.forEach((item,index)=>{ + console.log(item.device_id) + console.log(item.display_name) + }) + } + + UserProfileContent{ + id: userProfile } background: Item{ diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 2c9486a..dd637e5 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -267,11 +267,7 @@ ApplicationWindow { model: 7 delegate: Rectangle { color: "transparent" -<<<<<<< HEAD - implicitHeight: Qt.application.font.pixelSize * 3 -======= implicitHeight: Qt.application.font.pixelSize * 8 ->>>>>>> Add DeviceVerificationFlow dummy and verification test button implicitWidth: col.width ColumnLayout { id: col @@ -414,11 +410,7 @@ ApplicationWindow { property string title: "Verification timed out" ColumnLayout { spacing: 16 -<<<<<<< HEAD - Label { -======= Text { ->>>>>>> Add DeviceVerificationFlow dummy and verification test button Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index afd1acb..227b410 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -15,6 +15,7 @@ #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "../ui/UserProfile.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) @@ -86,6 +87,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin qmlRegisterType("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType("im.nheko", 1, 0, "DeviceVerificationFlow"); + qmlRegisterType("im.nheko",1,0,"UserProfileContent"); + qRegisterMetaType(); qRegisterMetaType(); qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel"); qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel"); @@ -467,9 +470,3 @@ TimelineViewManager::startDummyVerification() { emit deviceVerificationRequest(new DeviceVerificationFlow(this)); } - -void -TimelineViewManager::startDummyVerification() -{ - emit deviceVerificationRequest(new DeviceVerificationFlow(this)); -} diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index ac35f1d..3078569 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -1,28 +1,43 @@ #include "UserProfile.h" #include "Logging.h" -#include "MatrixClient.h" #include "Utils.h" +#include "mtx/responses/crypto.hpp" +#include UserProfile::UserProfile(QObject *parent) : QObject(parent) {} -QMap -UserProfile::getDeviceList() -{ +QVector +UserProfile::getDeviceList(){ + UserProfile::fetchDeviceList(this->userId); return this->deviceList; } +QString +UserProfile::getUserId (){ + return this->userId; +} + +void +UserProfile::setUserId (const QString &user_id){ + if(this->userId != userId) + return; + else + this->userId = user_id; +} + void -UserProfile::fetchDeviceList(const QString &userId) +UserProfile::fetchDeviceList(const QString &userID) { auto localUser = utils::localUser(); mtx::requests::QueryKeys req; - req.device_keys[userId.toStdString()] = {}; + mtx::responses::QueryKeys res; + req.device_keys[userID.toStdString()] = {}; http::client()->query_keys( req, - [user_id = userId.toStdString()](const mtx::responses::QueryKeys &res, + [user_id = userID.toStdString(),this](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {} {}", @@ -39,14 +54,18 @@ UserProfile::fetchDeviceList(const QString &userId) auto devices = res.device_keys.at(user_id); - std::vector deviceInfo; + QVector deviceInfo; for (const auto &d : devices) { auto device = d.second; // TODO: Verify signatures and ignore those that don't pass. - deviceInfo.emplace_back(DeviceInfo{ - QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name)}); + // std::cout<device_id = QString::fromStdString(d.first); + newdevice->display_name = QString::fromStdString(device.unsigned_info.device_display_name) + + deviceInfo.append(std::move(newdevice)); } std::sort(deviceInfo.begin(), @@ -54,5 +73,8 @@ UserProfile::fetchDeviceList(const QString &userId) [](const DeviceInfo &a, const DeviceInfo &b) { return a.device_id > b.device_id; }); + + this->deviceList = deviceInfo; + emit UserProfile::deviceListUpdated(); }); } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index d003e6c..bbf57c7 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -1,29 +1,49 @@ #pragma once -#include #include #include -struct DeviceInfo +#include "MatrixClient.h" + +class DeviceInfo { +public: + explicit DeviceInfo(QString device_id,QString display_name){ + this->device_id = device_id; + this->display_name = display_name; + } + ~DeviceInfo() = default; + DeviceInfo(const DeviceInfo &device){ + this->device_id = device.device_id; + this->display_name = device.display_name; + } + QString device_id; QString display_name; }; +Q_DECLARE_METATYPE(DeviceInfo); class UserProfile : public QObject { Q_OBJECT - Q_PROPERTY(QMap deviceList READ getDeviceList NOTIFY DeviceListUpdated) + Q_PROPERTY(QVector deviceList READ getDeviceList NOTIFY deviceListUpdated) + Q_PROPERTY(QString userId READ getUserId WRITE setUserId) public: + // constructor explicit UserProfile(QObject *parent = 0); - QMap getDeviceList(); + // getters + QVector getDeviceList(); + QString getUserId(); + // setters + void setUserId(const QString &userId); Q_INVOKABLE void fetchDeviceList(const QString &userID); signals: - void DeviceListUpdated(); + void deviceListUpdated(); private: - QMap deviceList; + QVector deviceList; + QString userId; }; \ No newline at end of file From f9c0f4dd5410621a8427e2ef21496b7791d44d2f Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Wed, 27 May 2020 14:19:26 +0530 Subject: [PATCH 09/70] Add C++ Model for DeviceList --- CMakeLists.txt | 2 + resources/qml/TimelineView.qml | 28 +++++------- resources/qml/UserProfile.qml | 58 ++++++++++++++---------- src/Olm.cpp | 2 - src/timeline/TimelineViewManager.cpp | 8 +++- src/ui/UserProfile.cpp | 46 +++++++++++-------- src/ui/UserProfile.h | 22 +++++----- src/ui/UserProfileModel.cpp | 66 ++++++++++++++++++++++++++++ src/ui/UserProfileModel.h | 29 ++++++++++++ 9 files changed, 188 insertions(+), 73 deletions(-) create mode 100644 src/ui/UserProfileModel.cpp create mode 100644 src/ui/UserProfileModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e170495..654eae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,7 @@ set(SRC_FILES src/ui/Theme.cpp src/ui/ThemeManager.cpp src/ui/UserProfile.cpp + src/ui/UserProfileModel.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp @@ -482,6 +483,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/Theme.h src/ui/ThemeManager.h src/ui/UserProfile.h + src/ui/UserProfileModel.h src/notifications/Manager.h diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index ed403aa..e4c820f 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -273,6 +273,9 @@ Page { color: colors.base } } + + property variant userProfile + Row { height: userName.height spacing: 8 @@ -287,8 +290,10 @@ Page { MouseArea { anchors.fill: parent onClicked: { - userProfile.user_data = modelData - userProfile.show() + if(userProfile) userProfile.destroy() + var component = Qt.createComponent("UserProfile.qml"); + userProfile = component.createObject(timelineRoot,{user_data : modelData}); + userProfile.show(); } cursorShape: Qt.PointingHandCursor propagateComposedEvents: true @@ -303,26 +308,17 @@ Page { MouseArea { anchors.fill: parent + Layout.alignment: Qt.AlignHCenter onClicked: { - userProfile.user_data = modelData - userProfile.show() + if(userProfile) userProfile.destroy() + var component = Qt.createComponent("UserProfile.qml") + userProfile = component.createObject(timelineRoot,{user_data : modelData}) + userProfile.show() } cursorShape: Qt.PointingHandCursor propagateComposedEvents: true } } - - Label { - color: colors.buttonText - text: timelineManager.userStatus(modelData.userId) - textFormat: Text.PlainText - elide: Text.ElideRight - width: chat.delegateMaxWidth - parent.spacing*2 - userName.implicitWidth - avatarSize - font.italic: true - } - UserProfile{ - id: userProfile - } } } } diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index ae91abc..f29fb4c 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -16,25 +16,16 @@ ApplicationWindow{ Layout.alignment: Qt.AlignHCenter palette: colors - onAfterRendering: { - userProfileAvatar.url = chat.model.avatarUrl(user_data.userId).replace("mxc://", "image://MxcImage/") - userProfileName.text = user_data.userName - matrixUserID.text = user_data.userId - userProfile.userId = user_data.userId - log_devices() - } - - function log_devices() - { - console.log(userProfile.deviceList); - userProfile.deviceList.forEach((item,index)=>{ - console.log(item.device_id) - console.log(item.display_name) - }) - } - - UserProfileContent{ - id: userProfile + UserProfileList{ + id: userProfileList + userId: user_data.userId + onUserIdChanged : { + console.log(userId) + userProfileList.updateDeviceList() + } + onDeviceListUpdated : { + modelDeviceList.deviceList = userProfileList + } } background: Item{ @@ -52,6 +43,7 @@ ApplicationWindow{ Avatar{ id: userProfileAvatar + url:chat.model.avatarUrl(user_data.userId).replace("mxc://", "image://MxcImage/") height: 130 width: 130 displayName: modelData.userName @@ -60,12 +52,14 @@ ApplicationWindow{ Label{ id: userProfileName + text: user_data.userName fontSizeMode: Text.HorizontalFit Layout.alignment: Qt.AlignHCenter } Label{ id: matrixUserID + text: user_data.userId fontSizeMode: Text.HorizontalFit Layout.alignment: Qt.AlignHCenter } @@ -78,9 +72,29 @@ ApplicationWindow{ ScrollBar.horizontal.policy: ScrollBar.AlwaysOn ScrollBar.vertical.policy: ScrollBar.AlwaysOn - Label { - text: "ABC" - font.pixelSize: 700 + ListView{ + id: deviceList + anchors.fill: parent + clip: true + spacing: 10 + + model: UserProfileModel{ + id: modelDeviceList + } + + delegate: RowLayout{ + width: parent.width + Text{ + Layout.fillWidth: true + color: colors.text + text: deviceID + } + Text{ + Layout.fillWidth: true + color:colors.text + text: displayName + } + } } } diff --git a/src/Olm.cpp b/src/Olm.cpp index 494bc20..74fbac9 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -56,7 +56,6 @@ handle_to_device_messages(const std::vectorwarn("validation error for olm message: {} {}", e.what(), j_msg.dump(2)); - } } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) { @@ -399,7 +398,6 @@ handle_key_request_message(const mtx::events::DeviceEventdebug("ignoring all key requests for room {}", req.content.room_id); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 227b410..f4d1c00 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "BlurhashProvider.h" #include "ChatPage.h" @@ -16,6 +17,8 @@ #include "emoji/EmojiModel.h" #include "emoji/Provider.h" #include "../ui/UserProfile.h" +#include "src/ui/UserProfile.h" +#include "src/ui/UserProfileModel.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) @@ -87,8 +90,9 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin qmlRegisterType("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType("im.nheko", 1, 0, "DeviceVerificationFlow"); - qmlRegisterType("im.nheko",1,0,"UserProfileContent"); - qRegisterMetaType(); + qmlRegisterType("im.nheko", 1, 0, "UserProfileModel"); + qmlRegisterType("im.nheko", 1, 0, "UserProfileList"); + qRegisterMetaType(); qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel"); qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel"); diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 3078569..588d696 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -2,29 +2,32 @@ #include "Logging.h" #include "Utils.h" #include "mtx/responses/crypto.hpp" -#include UserProfile::UserProfile(QObject *parent) : QObject(parent) {} QVector -UserProfile::getDeviceList(){ - UserProfile::fetchDeviceList(this->userId); +UserProfile::getDeviceList() +{ return this->deviceList; } QString -UserProfile::getUserId (){ +UserProfile::getUserId() +{ return this->userId; } void -UserProfile::setUserId (const QString &user_id){ - if(this->userId != userId) +UserProfile::setUserId(const QString &user_id) +{ + if (this->userId != userId) return; - else + else { this->userId = user_id; + emit UserProfile::userIdChanged(); + } } void @@ -37,11 +40,11 @@ UserProfile::fetchDeviceList(const QString &userID) http::client()->query_keys( req, - [user_id = userID.toStdString(),this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { + [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { if (err) { - nhlog::net()->warn("failed to query device keys: {} {}", - err->matrix_error.error, + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, static_cast(err->status_code)); return; } @@ -53,17 +56,16 @@ UserProfile::fetchDeviceList(const QString &userID) } auto devices = res.device_keys.at(user_id); - QVector deviceInfo; + for (const auto &d : devices) { auto device = d.second; // TODO: Verify signatures and ignore those that don't pass. - // std::cout<device_id = QString::fromStdString(d.first); - newdevice->display_name = QString::fromStdString(device.unsigned_info.device_display_name) + DeviceInfo newdevice( + QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name)); + QString::fromStdString(device.unsigned_info.device_display_name); deviceInfo.append(std::move(newdevice)); } @@ -74,7 +76,13 @@ UserProfile::fetchDeviceList(const QString &userID) return a.device_id > b.device_id; }); - this->deviceList = deviceInfo; - emit UserProfile::deviceListUpdated(); + this->deviceList = std::move(deviceInfo); + emit UserProfile::deviceListUpdated(); }); } + +void +UserProfile::updateDeviceList() +{ + fetchDeviceList(this->userId); +} diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index bbf57c7..c37e23a 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -2,33 +2,29 @@ #include #include +#include #include "MatrixClient.h" class DeviceInfo { public: - explicit DeviceInfo(QString device_id,QString display_name){ - this->device_id = device_id; - this->display_name = display_name; - } - ~DeviceInfo() = default; - DeviceInfo(const DeviceInfo &device){ - this->device_id = device.device_id; - this->display_name = device.display_name; - } + DeviceInfo(const QString deviceID, const QString displayName) + : device_id(deviceID) + , display_name(displayName) + {} + + DeviceInfo() {} QString device_id; QString display_name; }; -Q_DECLARE_METATYPE(DeviceInfo); class UserProfile : public QObject { Q_OBJECT + Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged) Q_PROPERTY(QVector deviceList READ getDeviceList NOTIFY deviceListUpdated) - Q_PROPERTY(QString userId READ getUserId WRITE setUserId) - public: // constructor explicit UserProfile(QObject *parent = 0); @@ -39,8 +35,10 @@ public: void setUserId(const QString &userId); Q_INVOKABLE void fetchDeviceList(const QString &userID); + Q_INVOKABLE void updateDeviceList(); signals: + void userIdChanged(); void deviceListUpdated(); private: diff --git a/src/ui/UserProfileModel.cpp b/src/ui/UserProfileModel.cpp new file mode 100644 index 0000000..ec0456c --- /dev/null +++ b/src/ui/UserProfileModel.cpp @@ -0,0 +1,66 @@ +#include "UserProfileModel.h" +#include "UserProfile.h" +#include + +UserProfileModel::UserProfileModel(QObject *parent) + : QAbstractListModel(parent) + , deviceList(nullptr) +{} + +int +UserProfileModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid() || !this->deviceList) + return 0; + return this->deviceList->getDeviceList().size(); +} + +QVariant +UserProfileModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || !this->deviceList) + return QVariant(); + + const DeviceInfo device = this->deviceList->getDeviceList().at(index.row()); + switch (role) { + case DEVICEID: + return QVariant(device.device_id); + case DISPLAYNAME: + return QVariant(device.display_name); + } + return QVariant(); +} + +QHash +UserProfileModel::roleNames() const +{ + QHash names; + names[DEVICEID] = "deviceID"; + names[DISPLAYNAME] = "displayName"; + return names; +} + +UserProfile * +UserProfileModel::getList() const +{ + return (this->deviceList); +} + +void +UserProfileModel::setList(UserProfile *devices) +{ + beginResetModel(); + + if (devices) + devices->disconnect(this); + + if (this->deviceList) { + const int index = this->deviceList->getDeviceList().size(); + beginInsertRows(QModelIndex(), index, index); + endInsertRows(); + } + + this->deviceList = devices; + + endResetModel(); +} \ No newline at end of file diff --git a/src/ui/UserProfileModel.h b/src/ui/UserProfileModel.h new file mode 100644 index 0000000..c21a806 --- /dev/null +++ b/src/ui/UserProfileModel.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class UserProfile; // forward declaration of the class UserProfile + +class UserProfileModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(UserProfile *deviceList READ getList WRITE setList) + +public: + explicit UserProfileModel(QObject *parent = nullptr); + + enum + { + DEVICEID, + DISPLAYNAME + }; + UserProfile *getList() const; + void setList(UserProfile *devices); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + virtual QHash roleNames() const override; + +private: + UserProfile *deviceList; +}; \ No newline at end of file From b628f485ff1cc66c20b9888b73e3427eb86d062e Mon Sep 17 00:00:00 2001 From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com> Date: Thu, 4 Jun 2020 19:14:15 +0530 Subject: [PATCH 10/70] Tweak UI for device verification and Add more slots --- resources/qml/UserProfile.qml | 38 +++++++--- .../DeviceVerification.qml | 41 ++++++++++- src/DeviceVerificationFlow.cpp | 70 ++++++++++++++++++- src/DeviceVerificationFlow.h | 10 ++- 4 files changed, 146 insertions(+), 13 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index f29fb4c..a85c41c 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -5,6 +5,8 @@ import QtQuick.Window 2.3 import im.nheko 1.0 +import "./device-verification" + ApplicationWindow{ property var user_data property var colors: currentActivePalette @@ -20,7 +22,6 @@ ApplicationWindow{ id: userProfileList userId: user_data.userId onUserIdChanged : { - console.log(userId) userProfileList.updateDeviceList() } onDeviceListUpdated : { @@ -84,15 +85,34 @@ ApplicationWindow{ delegate: RowLayout{ width: parent.width - Text{ - Layout.fillWidth: true - color: colors.text - text: deviceID + ColumnLayout{ + Text{ + Layout.fillWidth: true + color: colors.text + Layout.alignment: Qt.AlignRight + text: deviceID + } + Text{ + Layout.fillWidth: true + color:colors.text + Layout.alignment: Qt.AlignRight + text: displayName + } + Component { + id: deviceVerificationDialog + DeviceVerification {} + } + DeviceVerificationFlow { + id: deviceVerificationFlow + } } - Text{ - Layout.fillWidth: true - color:colors.text - text: displayName + Button{ + text:"Verify" + onClicked: { + var dialog = deviceVerificationDialog.createObject(userProfileDialog, + {flow: deviceVerificationFlow,sender: true}); + dialog.show(); + } } } } diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index dd637e5..ce2485f 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -7,6 +7,7 @@ import Qt.labs.settings 1.0 import im.nheko 1.0 ApplicationWindow { + property bool sender: true title: stack.currentItem.title id: dialog @@ -24,7 +25,7 @@ ApplicationWindow { width: stack.implicitWidth StackView { id: stack - initialItem: newVerificationRequest + initialItem: sender == true?newVerificationRequest:acceptNewVerificationRequest implicitWidth: currentItem.implicitWidth implicitHeight: currentItem.implicitHeight } @@ -47,7 +48,7 @@ ApplicationWindow { Component { id: newVerificationRequest Pane { - property string title: "Device Verification Request" + property string title: "Sending Device Verification Request" ColumnLayout { spacing: 16 Label { @@ -82,6 +83,42 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "Start verification" + onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.sendVerificationRequest(); } + } + } + } + } + } + + Component { + id: acceptNewVerificationRequest + Pane { + property string title: "Recieving Device Verification Request" + ColumnLayout { + spacing: 16 + + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: "The device was requested to be verified" + + verticalAlignment: Text.AlignVCenter + } + + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: "Deny" + onClicked: { dialog.close(); flow.cancelVerification(); } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: "Accept" onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.acceptVerificationRequest(); } } } diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 69d6ab9..12e31c0 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -1,5 +1,8 @@ #include "DeviceVerificationFlow.h" +#include +#include +#include // only for debugging #include static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes @@ -15,17 +18,80 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) timeout->start(TIMEOUT); } -//! accepts a verification and starts the verification flow +std::string +DeviceVerificationFlow::generate_txn_id() +{ + this->transaction_id = mtx::client::utils::random_token(32, false); + return this->transaction_id; +} + +//! accepts a verification void DeviceVerificationFlow::acceptVerificationRequest() { + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationAccept req; + + req.transaction_id = this->transaction_id; + req.method = mtx::events::msg::VerificationMethods::SASv1; + req.key_agreement_protocol = ""; + req.hash = ""; + req.message_authentication_code = ""; + // req.short_authentication_string = ""; + req.commitment = ""; + emit verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); + + // Yet to add send to_device message +} +//! starts the verification flow +void +DeviceVerificationFlow::startVerificationRequest() +{ + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationAccept req; + + // req.from_device = ""; + req.transaction_id = this->transaction_id; + req.method = mtx::events::msg::VerificationMethods::SASv1; + req.key_agreement_protocol = {}; + // req.hashes = {}; + req.message_authentication_code = {}; + // req.short_authentication_string = ""; + + // Yet to add send to_device message +} +//! sends a verification request +void +DeviceVerificationFlow::sendVerificationRequest() +{ + QDateTime CurrentTime = QDateTime::currentDateTimeUtc(); + + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationRequest req; + + req.from_device = ""; + req.transaction_id = generate_txn_id(); + req.methods.resize(1); + req.methods[0] = mtx::events::msg::VerificationMethods::SASv1; + req.timestamp = (uint64_t)CurrentTime.toTime_t(); + + // Yet to add send to_device message } //! cancels a verification flow void DeviceVerificationFlow::cancelVerification() { + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationCancel req; + + req.transaction_id = this->transaction_id; + req.reason = ""; + req.code = ""; + this->deleteLater(); + + // Yet to add send to_device message } //! Completes the verification flow void @@ -33,4 +99,6 @@ DeviceVerificationFlow::acceptDevice() { emit deviceVerified(); this->deleteLater(); + + // Yet to add send to_device message } diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 038f1e1..71c40cd 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -20,8 +20,12 @@ public: DeviceVerificationFlow(QObject *parent = nullptr); public slots: - //! accepts a verification and starts the verification flow + //! sends a verification request + void sendVerificationRequest(); + //! accepts a verification void acceptVerificationRequest(); + //! starts the verification flow + void startVerificationRequest(); //! cancels a verification flow void cancelVerification(); //! Completes the verification flow @@ -34,5 +38,9 @@ signals: void verificationCanceled(); private: + //! generates a unique transaction id + std::string generate_txn_id(); + QTimer *timeout = nullptr; + std::string transaction_id; }; From cd5dd0e39b23c5a258d0f6811f6b5987d8f6f391 Mon Sep 17 00:00:00 2001 From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com> Date: Sun, 7 Jun 2020 17:05:32 +0530 Subject: [PATCH 11/70] Add SAS Method choice and Add send_to_device API call --- resources/qml/UserProfile.qml | 19 ++- .../DeviceVerification.qml | 16 +++ src/DeviceVerificationFlow.cpp | 131 ++++++++++++++---- src/DeviceVerificationFlow.h | 16 ++- 4 files changed, 151 insertions(+), 31 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index a85c41c..6bfee09 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -107,18 +107,28 @@ ApplicationWindow{ } } Button{ + id: verifyButton text:"Verify" onClicked: { var dialog = deviceVerificationDialog.createObject(userProfileDialog, - {flow: deviceVerificationFlow,sender: true}); + {flow: deviceVerificationFlow,sender: false}); + deviceVerificationFlow.userId = user_data.userId + deviceVerificationFlow.deviceId = model.deviceID dialog.show(); } + contentItem: Text { + text: verifyButton.text + color: colors.background + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } } } } } Button{ + id: okbutton text:"OK" onClicked: userProfileDialog.close() anchors.margins: { @@ -126,6 +136,13 @@ ApplicationWindow{ bottom:10 } + contentItem: Text { + text: okbutton.text + color: colors.background + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + Layout.alignment: Qt.AlignRight | Qt.AlignBottom } } diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index ce2485f..31f6f9c 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -107,6 +107,22 @@ ApplicationWindow { verticalAlignment: Text.AlignVCenter } + RowLayout { + RadioButton { + Layout.alignment: Qt.AlignLeft + text: "Decimal" + onClicked: { flow.method = DeviceVerificationFlow.Decimal } + } + Item { + Layout.fillWidth: true + } + RadioButton { + Layout.alignment: Qt.AlignRight + text: "Emoji" + onClicked: { flow.method = DeviceVerificationFlow.Emoji } + } + } + RowLayout { Button { Layout.alignment: Qt.AlignLeft diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 12e31c0..5bbe2a7 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -1,9 +1,10 @@ #include "DeviceVerificationFlow.h" -#include +#include "Logging.h" #include #include // only for debugging #include +#include // only for debugging static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes @@ -18,11 +19,42 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) timeout->start(TIMEOUT); } -std::string -DeviceVerificationFlow::generate_txn_id() +QString +DeviceVerificationFlow::getUserId() { - this->transaction_id = mtx::client::utils::random_token(32, false); - return this->transaction_id; + toClient = mtx::identifiers::parse((this->userId).toStdString()); + std::cout << http::client()->device_id() << std::endl; + return this->userId; +} + +QString +DeviceVerificationFlow::getDeviceId() +{ + return this->deviceId; +} + +DeviceVerificationFlow::Method +DeviceVerificationFlow::getMethod() +{ + return this->method; +} + +void +DeviceVerificationFlow::setUserId(QString userID) +{ + this->userId = userID; +} + +void +DeviceVerificationFlow::setDeviceId(QString deviceID) +{ + this->deviceId = deviceID; +} + +void +DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_) +{ + this->method = method_; } //! accepts a verification @@ -34,32 +66,53 @@ DeviceVerificationFlow::acceptVerificationRequest() req.transaction_id = this->transaction_id; req.method = mtx::events::msg::VerificationMethods::SASv1; - req.key_agreement_protocol = ""; - req.hash = ""; + req.key_agreement_protocol = "curve25519"; + req.hash = "sha256"; req.message_authentication_code = ""; // req.short_authentication_string = ""; req.commitment = ""; - emit verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); + emit this->verificationRequestAccepted(this->method); - // Yet to add send to_device message + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + "m.key.verification.accept", body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to accept verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + // emit this->verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); + }); } //! starts the verification flow void DeviceVerificationFlow::startVerificationRequest() { - mtx::requests::ToDeviceMessages body; - mtx::events::msg::KeyVerificationAccept req; - - // req.from_device = ""; - req.transaction_id = this->transaction_id; - req.method = mtx::events::msg::VerificationMethods::SASv1; - req.key_agreement_protocol = {}; - // req.hashes = {}; - req.message_authentication_code = {}; + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationStart req; + + req.from_device = http::client()->device_id(); + req.transaction_id = this->transaction_id; + req.method = mtx::events::msg::VerificationMethods::SASv1; + req.key_agreement_protocols = {}; + req.hashes = {}; + req.message_authentication_codes = {}; // req.short_authentication_string = ""; - // Yet to add send to_device message + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + "m.key.verification.start", body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to start verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); } //! sends a verification request void @@ -70,13 +123,25 @@ DeviceVerificationFlow::sendVerificationRequest() mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationRequest req; - req.from_device = ""; - req.transaction_id = generate_txn_id(); + this->transaction_id = http::client()->generate_txn_id(); + + req.from_device = http::client()->device_id(); + req.transaction_id = this->transaction_id; req.methods.resize(1); req.methods[0] = mtx::events::msg::VerificationMethods::SASv1; req.timestamp = (uint64_t)CurrentTime.toTime_t(); - // Yet to add send to_device message + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + "m.key.verification.request", body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); } //! cancels a verification flow void @@ -86,12 +151,22 @@ DeviceVerificationFlow::cancelVerification() mtx::events::msg::KeyVerificationCancel req; req.transaction_id = this->transaction_id; - req.reason = ""; - req.code = ""; - - this->deleteLater(); - - // Yet to add send to_device message + // TODO: Add Proper Error Messages and Code + req.reason = "Device Verification Cancelled"; + req.code = "400"; + + body[this->toClient][deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + "m.key.verification.cancel", body, [this](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to cancel verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + this->deleteLater(); + }); } //! Completes the verification flow void diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 71c40cd..c770119 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -1,5 +1,6 @@ #pragma once +#include #include class QTimer; @@ -8,6 +9,9 @@ class DeviceVerificationFlow : public QObject { Q_OBJECT // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + Q_PROPERTY(QString userId READ getUserId WRITE setUserId) + Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId) + Q_PROPERTY(Method method READ getMethod WRITE setMethod) public: enum Method @@ -18,6 +22,12 @@ public: Q_ENUM(Method) DeviceVerificationFlow(QObject *parent = nullptr); + QString getUserId(); + QString getDeviceId(); + Method getMethod(); + void setUserId(QString userID); + void setDeviceId(QString deviceID); + void setMethod(Method method_); public slots: //! sends a verification request @@ -38,9 +48,11 @@ signals: void verificationCanceled(); private: - //! generates a unique transaction id - std::string generate_txn_id(); + QString userId; + QString deviceId; + Method method; QTimer *timeout = nullptr; std::string transaction_id; + mtx::identifiers::User toClient; }; From 1eb162cb6fa81c7388725414e9c68f5991372d08 Mon Sep 17 00:00:00 2001 From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com> Date: Tue, 9 Jun 2020 22:06:41 +0530 Subject: [PATCH 12/70] Handle Device Verification related to_device messages --- resources/qml/UserProfile.qml | 19 ++-- .../DeviceVerification.qml | 2 +- src/ChatPage.cpp | 1 + src/ChatPage.h | 12 ++ src/DeviceVerificationFlow.cpp | 105 +++++++++++++++++- src/DeviceVerificationFlow.h | 5 + src/Olm.cpp | 23 +++- 7 files changed, 153 insertions(+), 14 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 6bfee09..c40e676 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -29,6 +29,14 @@ ApplicationWindow{ } } + Component { + id: deviceVerificationDialog + DeviceVerification {} + } + DeviceVerificationFlow { + id: deviceVerificationFlow + } + background: Item{ id: userProfileItem width: userProfileDialog.width @@ -98,22 +106,15 @@ ApplicationWindow{ Layout.alignment: Qt.AlignRight text: displayName } - Component { - id: deviceVerificationDialog - DeviceVerification {} - } - DeviceVerificationFlow { - id: deviceVerificationFlow - } } Button{ id: verifyButton text:"Verify" onClicked: { - var dialog = deviceVerificationDialog.createObject(userProfileDialog, - {flow: deviceVerificationFlow,sender: false}); deviceVerificationFlow.userId = user_data.userId deviceVerificationFlow.deviceId = model.deviceID + var dialog = deviceVerificationDialog.createObject(userProfileDialog, + {flow: deviceVerificationFlow,sender: true}); dialog.show(); } contentItem: Text { diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 31f6f9c..fca360f 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -83,7 +83,7 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "Start verification" - onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.sendVerificationRequest(); } + onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.startVerificationRequest(); } } } } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 518be31..17ea255 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -27,6 +27,7 @@ #include "Cache_p.h" #include "ChatPage.h" #include "EventAccessors.h" +#include "DeviceVerificationFlow.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" diff --git a/src/ChatPage.h b/src/ChatPage.h index 18bed28..b05a388 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -164,6 +165,17 @@ signals: void themeChanged(); void decryptSidebarChanged(); + //! Signals for device verificaiton + void recievedDeviceVerificationAccept( + const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationRequest( + const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationCancel( + const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationKey(const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationMac(const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationStart(const mtx::events::collections::DeviceEvents &message); + private slots: void showUnreadMessageNotification(int count); void updateTopBarAvatar(const QString &roomid, const QString &img); diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 5bbe2a7..c6652d0 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -1,4 +1,5 @@ #include "DeviceVerificationFlow.h" +#include "ChatPage.h" #include "Logging.h" #include @@ -8,22 +9,73 @@ static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes +namespace msgs = mtx::events::msg; + DeviceVerificationFlow::DeviceVerificationFlow(QObject *) { + qRegisterMetaType(); timeout = new QTimer(this); timeout->setSingleShot(true); connect(timeout, &QTimer::timeout, this, [this]() { emit timedout(); this->deleteLater(); }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationAccept, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + std::cout << "Recieved Event Accept" << std::endl; + } + }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationRequest, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + std::cout << "Recieved Event Request" << std::endl; + } + }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationCancel, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + std::cout << "Recieved Event Cancel" << std::endl; + } + }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationKey, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + std::cout << "Recieved Event Key" << std::endl; + } + }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationMac, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + std::cout << "Recieved Event Mac" << std::endl; + } + }); timeout->start(TIMEOUT); } QString DeviceVerificationFlow::getUserId() { - toClient = mtx::identifiers::parse((this->userId).toStdString()); - std::cout << http::client()->device_id() << std::endl; return this->userId; } @@ -43,6 +95,7 @@ void DeviceVerificationFlow::setUserId(QString userID) { this->userId = userID; + this->toClient = mtx::identifiers::parse(userID.toStdString()); } void @@ -101,7 +154,8 @@ DeviceVerificationFlow::startVerificationRequest() req.hashes = {}; req.message_authentication_codes = {}; // req.short_authentication_string = ""; - + qDebug()<<"Inside Start Verification"; + qDebug()<userId; body[this->toClient][this->deviceId.toStdString()] = req; http::client() @@ -168,6 +222,51 @@ DeviceVerificationFlow::cancelVerification() this->deleteLater(); }); } +//! sends the verification key +void +DeviceVerificationFlow::sendVerificationKey() +{ + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationKey req; + + req.key = ""; + req.transaction_id = this->transaction_id; + + body[this->toClient][deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + "m.key.verification.cancel", body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification key: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); +} +//! sends the mac of the keys +void +DeviceVerificationFlow::sendVerificationMac() +{ + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationMac req; + + req.transaction_id = this->transaction_id; + // req.mac = ""; + req.keys = ""; + + body[this->toClient][deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + "m.key.verification.cancel", body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification MAC: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); +} //! Completes the verification flow void DeviceVerificationFlow::acceptDevice() diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index c770119..561a371 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -38,6 +38,10 @@ public slots: void startVerificationRequest(); //! cancels a verification flow void cancelVerification(); + //! sends the verification key + void sendVerificationKey(); + //! sends the mac of the keys + void sendVerificationMac(); //! Completes the verification flow void acceptDevice(); @@ -56,3 +60,4 @@ private: std::string transaction_id; mtx::identifiers::User toClient; }; +Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents) \ No newline at end of file diff --git a/src/Olm.cpp b/src/Olm.cpp index 74fbac9..6c1d3fd 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1,11 +1,15 @@ +#include #include #include "Olm.h" #include "Cache.h" +#include "ChatPage.h" #include "Logging.h" #include "MatrixClient.h" #include "Utils.h" +#include +#include // only for debugging static const std::string STORAGE_SECRET_KEY("secret"); constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2"; @@ -27,7 +31,6 @@ handle_to_device_messages(const std::vectorinfo("received {} to_device messages", msgs.size()); nlohmann::json j_msg; @@ -74,6 +77,24 @@ handle_to_device_messages(const std::vectorrecievedDeviceVerificationAccept(msg); + std::cout << j_msg.dump(2) << std::endl; + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { + ChatPage::instance()->recievedDeviceVerificationRequest(msg); + std::cout << j_msg.dump(2) << std::endl; + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { + ChatPage::instance()->recievedDeviceVerificationCancel(msg); + std::cout << j_msg.dump(2) << std::endl; + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { + ChatPage::instance()->recievedDeviceVerificationKey(msg); + std::cout << j_msg.dump(2) << std::endl; + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { + ChatPage::instance()->recievedDeviceVerificationMac(msg); + std::cout << j_msg.dump(2) << std::endl; + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { + ChatPage::instance()->recievedDeviceVerificationStart(msg); + std::cout << j_msg.dump(2) << std::endl; } else { nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); } From 41b6ef0c32877384d4157a87aee01f67090b15a3 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Wed, 17 Jun 2020 23:58:35 +0530 Subject: [PATCH 13/70] Add DeviceVerificationList to keep track of all flows and Popup on recieving start or request --- resources/qml/TimelineView.qml | 18 +++-- resources/qml/UserProfile.qml | 10 ++- .../DeviceVerification.qml | 60 ++++++++++++--- src/ChatPage.cpp | 2 +- src/DeviceVerificationFlow.cpp | 69 +++++++++++------ src/DeviceVerificationFlow.h | 10 ++- src/emoji/EmojiModel.h | 1 - src/timeline/TimelineViewManager.cpp | 75 +++++++++++++++---- src/timeline/TimelineViewManager.h | 20 ++++- 9 files changed, 203 insertions(+), 62 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e4c820f..5170a41 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -103,20 +103,22 @@ Page { id: deviceVerificationDialog DeviceVerification {} } + Component{ + id: deviceVerificationFlow + DeviceVerificationFlow {} + } Connections { target: timelineManager - onDeviceVerificationRequest: { - var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: deviceVerificationFlow}); + onNewDeviceVerificationRequest: { + var newFlow = deviceVerificationFlow.createObject(timelineRoot, + {userId : userId,sender: false,deviceId : deviceId,tranId:transactionId}); + deviceVerificationList.add(newFlow.tranId); + var dialog = deviceVerificationDialog.createObject(timelineRoot, + {flow: newFlow,sender: false}); dialog.show(); } } - Button { - text: "test device verification" - onClicked: timelineManager.startDummyVerification() - z: 5 - } - Label { visible: !timelineManager.timeline && !timelineManager.isInitialSync anchors.centerIn: parent diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index c40e676..80415a2 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -33,8 +33,9 @@ ApplicationWindow{ id: deviceVerificationDialog DeviceVerification {} } - DeviceVerificationFlow { + Component{ id: deviceVerificationFlow + DeviceVerificationFlow {} } background: Item{ @@ -111,10 +112,11 @@ ApplicationWindow{ id: verifyButton text:"Verify" onClicked: { - deviceVerificationFlow.userId = user_data.userId - deviceVerificationFlow.deviceId = model.deviceID + var newFlow = deviceVerificationFlow.createObject(userProfileDialog, + {userId : user_data.userId,sender: true,deviceId : model.deviceID}); + deviceVerificationList.add(newFlow.tranId); var dialog = deviceVerificationDialog.createObject(userProfileDialog, - {flow: deviceVerificationFlow,sender: true}); + {flow: newFlow,sender: true}); dialog.show(); } contentItem: Text { diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index fca360f..ad0edeb 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -75,7 +75,12 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignLeft text: "Cancel" - onClicked: { dialog.close(); flow.cancelVerification(); } + onClicked: { + dialog.close(); + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } } Item { Layout.fillWidth: true @@ -127,7 +132,12 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignLeft text: "Deny" - onClicked: { dialog.close(); flow.cancelVerification(); } + onClicked: { + dialog.close(); + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } } Item { Layout.fillWidth: true @@ -166,7 +176,12 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignLeft text: "Cancel" - onClicked: { dialog.close(); flow.cancelVerification(); } + onClicked: { + dialog.close(); + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } } Item { Layout.fillWidth: true @@ -212,7 +227,12 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignLeft text: "They do not match!" - onClicked: { dialog.close(); flow.cancelVerification(); } + onClicked: { + dialog.close(); + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } } Item { Layout.fillWidth: true @@ -346,7 +366,12 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignLeft text: "They do not match!" - onClicked: { dialog.close(); flow.cancelVerification(); } + onClicked: { + dialog.close(); + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } } Item { Layout.fillWidth: true @@ -385,7 +410,12 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignLeft text: "Cancel" - onClicked: { dialog.close(); flow.cancelVerification(); } + onClicked: { + dialog.close(); + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } } Item { Layout.fillWidth: true @@ -419,7 +449,11 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "Close" - onClicked: dialog.close() + onClicked: { + dialog.close() + deviceVerificationList.remove(flow.tranId); + delete flow; + } } } } @@ -450,7 +484,11 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "Close" - onClicked: dialog.close() + onClicked: { + dialog.close() + deviceVerificationList.remove(flow.tranId); + delete flow; + } } } } @@ -481,7 +519,11 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "Close" - onClicked: dialog.close() + onClicked: { + dialog.close() + deviceVerificationList.remove(flow.tranId); + delete flow; + } } } } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 17ea255..aba1f75 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -26,8 +26,8 @@ #include "Cache.h" #include "Cache_p.h" #include "ChatPage.h" -#include "EventAccessors.h" #include "DeviceVerificationFlow.h" +#include "EventAccessors.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index c6652d0..efb9882 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -1,7 +1,7 @@ #include "DeviceVerificationFlow.h" #include "ChatPage.h" - #include "Logging.h" + #include #include // only for debugging #include @@ -13,9 +13,10 @@ namespace msgs = mtx::events::msg; DeviceVerificationFlow::DeviceVerificationFlow(QObject *) { - qRegisterMetaType(); timeout = new QTimer(this); timeout->setSingleShot(true); + if (this->sender == true) + this->transaction_id = http::client()->generate_txn_id(); connect(timeout, &QTimer::timeout, this, [this]() { emit timedout(); this->deleteLater(); @@ -73,6 +74,12 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) timeout->start(TIMEOUT); } +QString +DeviceVerificationFlow::getTransactionId() +{ + return QString::fromStdString(this->transaction_id); +} + QString DeviceVerificationFlow::getUserId() { @@ -91,10 +98,22 @@ DeviceVerificationFlow::getMethod() return this->method; } +bool +DeviceVerificationFlow::getSender() +{ + return this->sender; +} + +void +DeviceVerificationFlow::setTransactionId(QString transaction_id_) +{ + this->transaction_id = transaction_id_.toStdString(); +} + void DeviceVerificationFlow::setUserId(QString userID) { - this->userId = userID; + this->userId = userID; this->toClient = mtx::identifiers::parse(userID.toStdString()); } @@ -110,6 +129,12 @@ DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_) this->method = method_; } +void +DeviceVerificationFlow::setSender(bool sender_) +{ + this->sender = sender_; +} + //! accepts a verification void DeviceVerificationFlow::acceptVerificationRequest() @@ -119,11 +144,12 @@ DeviceVerificationFlow::acceptVerificationRequest() req.transaction_id = this->transaction_id; req.method = mtx::events::msg::VerificationMethods::SASv1; - req.key_agreement_protocol = "curve25519"; + req.key_agreement_protocol = "curve25519-hkdf-sha256"; req.hash = "sha256"; - req.message_authentication_code = ""; - // req.short_authentication_string = ""; - req.commitment = ""; + req.message_authentication_code = "hkdf-hmac-sha256"; + req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, + mtx::events::msg::SASMethods::Emoji}; + req.commitment = ""; emit this->verificationRequestAccepted(this->method); @@ -132,12 +158,12 @@ DeviceVerificationFlow::acceptVerificationRequest() http::client() ->send_to_device( - "m.key.verification.accept", body, [](mtx::http::RequestErr err) { + this->transaction_id, body, [this](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to accept verification request: {} {}", err->matrix_error.error, static_cast(err->status_code)); - // emit this->verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); + emit this->verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); }); } //! starts the verification flow @@ -150,22 +176,23 @@ DeviceVerificationFlow::startVerificationRequest() req.from_device = http::client()->device_id(); req.transaction_id = this->transaction_id; req.method = mtx::events::msg::VerificationMethods::SASv1; - req.key_agreement_protocols = {}; - req.hashes = {}; - req.message_authentication_codes = {}; - // req.short_authentication_string = ""; - qDebug()<<"Inside Start Verification"; - qDebug()<userId; + req.key_agreement_protocols = {"curve25519-hkdf-sha256"}; + req.hashes = {"sha256"}; + req.message_authentication_codes = {"hkdf-hmac-sha256", "hmac-sha256"}; + req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, + mtx::events::msg::SASMethods::Emoji}; + body[this->toClient][this->deviceId.toStdString()] = req; http::client() ->send_to_device( - "m.key.verification.start", body, [](mtx::http::RequestErr err) { + this->transaction_id, body, [body](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to start verification request: {} {}", err->matrix_error.error, static_cast(err->status_code)); + std::cout << nlohmann::json(body).dump(2) << std::endl; }); } //! sends a verification request @@ -177,8 +204,6 @@ DeviceVerificationFlow::sendVerificationRequest() mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationRequest req; - this->transaction_id = http::client()->generate_txn_id(); - req.from_device = http::client()->device_id(); req.transaction_id = this->transaction_id; req.methods.resize(1); @@ -190,7 +215,7 @@ DeviceVerificationFlow::sendVerificationRequest() http::client() ->send_to_device( - "m.key.verification.request", body, [](mtx::http::RequestErr err) { + this->transaction_id, body, [](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to send verification request: {} {}", err->matrix_error.error, @@ -214,7 +239,7 @@ DeviceVerificationFlow::cancelVerification() http::client() ->send_to_device( - "m.key.verification.cancel", body, [this](mtx::http::RequestErr err) { + this->transaction_id, body, [this](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to cancel verification request: {} {}", err->matrix_error.error, @@ -237,7 +262,7 @@ DeviceVerificationFlow::sendVerificationKey() http::client() ->send_to_device( - "m.key.verification.cancel", body, [](mtx::http::RequestErr err) { + this->transaction_id, body, [](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to send verification key: {} {}", err->matrix_error.error, @@ -260,7 +285,7 @@ DeviceVerificationFlow::sendVerificationMac() http::client() ->send_to_device( - "m.key.verification.cancel", body, [](mtx::http::RequestErr err) { + this->transaction_id, body, [](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to send verification MAC: {} {}", err->matrix_error.error, diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 561a371..b651394 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -1,5 +1,7 @@ #pragma once +#include "Olm.h" + #include #include @@ -9,6 +11,8 @@ class DeviceVerificationFlow : public QObject { Q_OBJECT // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + Q_PROPERTY(QString tranId READ getTransactionId WRITE setTransactionId) + Q_PROPERTY(bool sender READ getSender WRITE setSender) Q_PROPERTY(QString userId READ getUserId WRITE setUserId) Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId) Q_PROPERTY(Method method READ getMethod WRITE setMethod) @@ -22,12 +26,16 @@ public: Q_ENUM(Method) DeviceVerificationFlow(QObject *parent = nullptr); + QString getTransactionId(); QString getUserId(); QString getDeviceId(); Method getMethod(); + void setTransactionId(QString transaction_id_); + bool getSender(); void setUserId(QString userID); void setDeviceId(QString deviceID); void setMethod(Method method_); + void setSender(bool sender_); public slots: //! sends a verification request @@ -55,9 +63,9 @@ private: QString userId; QString deviceId; Method method; + bool sender; QTimer *timeout = nullptr; std::string transaction_id; mtx::identifiers::User toClient; }; -Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents) \ No newline at end of file diff --git a/src/emoji/EmojiModel.h b/src/emoji/EmojiModel.h index 8d43e00..88bacde 100644 --- a/src/emoji/EmojiModel.h +++ b/src/emoji/EmojiModel.h @@ -60,5 +60,4 @@ private: EmojiCategory category_ = EmojiCategory::Search; emoji::Provider emoji_provider_; }; - } \ No newline at end of file diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index f4d1c00..22fe4d6 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -16,12 +16,38 @@ #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" -#include "../ui/UserProfile.h" #include "src/ui/UserProfile.h" #include "src/ui/UserProfileModel.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) +namespace msgs = mtx::events::msg; + +void +DeviceVerificationList::add(QString tran_id) +{ + this->dv_list.push_back(tran_id); +} +void +DeviceVerificationList::remove(QString tran_id) +{ + for (QVector::iterator it = 0; it != (this->dv_list).end(); ++it) { + if (*it == tran_id) { + this->dv_list.erase(it); + break; + } + } +} +bool +DeviceVerificationList::exist(QString tran_id) +{ + for (int i = 0; i < (this->dv_list).size(); ++i) { + if (dv_list[i] == tran_id) + return true; + } + return false; +} + void TimelineViewManager::updateEncryptedDescriptions() { @@ -63,12 +89,12 @@ TimelineViewManager::userColor(QString id, QColor background) return userColors.value(id); } -QString -TimelineViewManager::userPresence(QString id) const -{ - return QString::fromStdString( - mtx::presence::to_string(cache::presenceState(id.toStdString()))); -} +// QString +// TimelineViewManager::userPresence(QString id) const +// { +// return QString::fromStdString( +// mtx::presence::to_string(cache::presenceState(id.toStdString()))); +// } QString TimelineViewManager::userStatus(QString id) const { @@ -81,6 +107,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin , blurhashProvider(new BlurhashProvider()) , settings(userSettings) { + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", 1, @@ -106,6 +133,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin 0, "EmojiCategory", "Error: Only enums"); + this->dvList = new DeviceVerificationList; #ifdef USE_QUICK_VIEW view = new QQuickView(); @@ -127,6 +155,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin container->setMinimumSize(200, 200); view->rootContext()->setContextProperty("timelineManager", this); view->rootContext()->setContextProperty("settings", settings.data()); + view->rootContext()->setContextProperty("deviceVerificationList", this->dvList); updateColorPalette(); view->engine()->addImageProvider("MxcImage", imgProvider); view->engine()->addImageProvider("colorimage", colorImgProvider); @@ -141,6 +170,32 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin &ChatPage::decryptSidebarChanged, this, &TimelineViewManager::updateEncryptedDescriptions); + connect(dynamic_cast(parent), + &ChatPage::recievedDeviceVerificationRequest, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + QString tranID = QString::fromStdString(msg.content.transaction_id); + QString deviceId = QString::fromStdString(msg.content.from_device); + QString userId = QString::fromStdString(msg.sender); + if (!(this->dvList->exist(tranID))) { + emit newDeviceVerificationRequest(tranID, userId, deviceId); + } + }); + connect(dynamic_cast(parent), + &ChatPage::recievedDeviceVerificationStart, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + QString tranID = QString::fromStdString(msg.content.transaction_id); + QString deviceId = QString::fromStdString(msg.content.from_device); + QString userId = QString::fromStdString(msg.sender); + if (!(this->dvList->exist(tranID))) { + emit newDeviceVerificationRequest(tranID, userId, deviceId); + } + }); } void @@ -468,9 +523,3 @@ TimelineViewManager::queueVideoMessage(const QString &roomid, model->sendMessage(video); } - -void -TimelineViewManager::startDummyVerification() -{ - emit deviceVerificationRequest(new DeviceVerificationFlow(this)); -} diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 583a9e4..7091271 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -22,6 +22,18 @@ class BlurhashProvider; class ColorImageProvider; class UserSettings; +class DeviceVerificationList : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE void add(QString tran_id); + Q_INVOKABLE void remove(QString tran_id); + Q_INVOKABLE bool exist(QString tran_id); + +private: + QVector dv_list; +}; + class TimelineViewManager : public QObject { Q_OBJECT @@ -44,9 +56,8 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); - Q_INVOKABLE void startDummyVerification(); - Q_INVOKABLE QString userPresence(QString id) const; + // Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; signals: @@ -56,7 +67,7 @@ signals: void initialSyncChanged(bool isInitialSync); void replyingEventChanged(QString replyingEvent); void replyClosed(); - void deviceVerificationRequest(DeviceVerificationFlow *deviceVerificationFlow); + void newDeviceVerificationRequest(QString transactionId, QString userId, QString deviceId); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); @@ -114,4 +125,7 @@ private: QSharedPointer settings; QHash userColors; + + DeviceVerificationList *dvList; }; +Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents) \ No newline at end of file From 67367d0004abe54445c9c6ca354c787fc0af8a79 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Sat, 20 Jun 2020 17:50:43 +0530 Subject: [PATCH 14/70] Shared secret with decimal and emoji works! --- resources/qml/TimelineView.qml | 13 +- .../DeviceVerification.qml | 26 +-- src/DeviceVerificationFlow.cpp | 167 ++++++++++++++---- src/DeviceVerificationFlow.h | 10 ++ src/timeline/TimelineViewManager.cpp | 64 ++++--- src/timeline/TimelineViewManager.h | 5 +- 6 files changed, 202 insertions(+), 83 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 5170a41..99d1468 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -103,18 +103,15 @@ Page { id: deviceVerificationDialog DeviceVerification {} } - Component{ - id: deviceVerificationFlow - DeviceVerificationFlow {} - } Connections { target: timelineManager onNewDeviceVerificationRequest: { - var newFlow = deviceVerificationFlow.createObject(timelineRoot, - {userId : userId,sender: false,deviceId : deviceId,tranId:transactionId}); - deviceVerificationList.add(newFlow.tranId); + flow.userId = userId; + flow.sender = false; + flow.deviceId = deviceId; + flow.tranId = transactionId; var dialog = deviceVerificationDialog.createObject(timelineRoot, - {flow: newFlow,sender: false}); + {flow: flow,sender: false}); dialog.show(); } } diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index ad0edeb..316fbe4 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -78,7 +78,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -135,7 +135,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -179,7 +179,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -211,15 +211,15 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter Label { font.pixelSize: Qt.application.font.pixelSize * 2 - text: "1234" + text: flow.sasList[0] } Label { font.pixelSize: Qt.application.font.pixelSize * 2 - text: "1234" + text: flow.sasList[1] } Label { font.pixelSize: Qt.application.font.pixelSize * 2 - text: "1234" + text: flow.sasList[2] } } @@ -230,7 +230,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -345,7 +345,7 @@ ApplicationWindow { ColumnLayout { id: col anchors.bottom: parent.bottom - property var emoji: emojis.mapping[Math.floor(Math.random()*64)] + property var emoji: emojis.mapping[flow.sasList[index]] Label { //height: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter @@ -369,7 +369,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -413,7 +413,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -451,7 +451,7 @@ ApplicationWindow { text: "Close" onClicked: { dialog.close() - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -486,7 +486,7 @@ ApplicationWindow { text: "Close" onClicked: { dialog.close() - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -521,7 +521,7 @@ ApplicationWindow { text: "Close" onClicked: { dialog.close() - deviceVerificationList.remove(flow.tranId); + // deviceVerificationList.remove(flow.tranId); delete flow; } } diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index efb9882..607cc27 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -15,32 +15,80 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) { timeout = new QTimer(this); timeout->setSingleShot(true); - if (this->sender == true) - this->transaction_id = http::client()->generate_txn_id(); + this->sas = olm::client()->sas_init(); connect(timeout, &QTimer::timeout, this, [this]() { emit timedout(); this->deleteLater(); }); + connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationAccept, - this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - std::cout << "Recieved Event Accept" << std::endl; - } - }); - connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationRequest, + &ChatPage::recievedDeviceVerificationStart, this, [this](const mtx::events::collections::DeviceEvents &message) { auto msg = - std::get>(message); + std::get>(message); if (msg.content.transaction_id == this->transaction_id) { - std::cout << "Recieved Event Request" << std::endl; + if (std::find(msg.content.key_agreement_protocols.begin(), + msg.content.key_agreement_protocols.end(), + "curve25519-hkdf-sha256") != + msg.content.key_agreement_protocols.end() && + std::find(msg.content.hashes.begin(), + msg.content.hashes.end(), + "sha256") != msg.content.hashes.end() && + (std::find(msg.content.message_authentication_codes.begin(), + msg.content.message_authentication_codes.end(), + "hmac-sha256") != + msg.content.message_authentication_codes.end() || + std::find(msg.content.message_authentication_codes.begin(), + msg.content.message_authentication_codes.end(), + "hkdf-hmac-sha256") != + msg.content.message_authentication_codes.end()) && + (std::find(msg.content.short_authentication_string.begin(), + msg.content.short_authentication_string.end(), + mtx::events::msg::SASMethods::Decimal) != + msg.content.short_authentication_string.end() || + std::find(msg.content.short_authentication_string.begin(), + msg.content.short_authentication_string.end(), + mtx::events::msg::SASMethods::Emoji) != + msg.content.short_authentication_string.end())) { + this->sendVerificationKey(); // Not sure about this maybe + // those optional methods + this->canonical_json = nlohmann::json(msg); + } else { + this->cancelVerification(); + } } }); + connect( + ChatPage::instance(), + &ChatPage::recievedDeviceVerificationAccept, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + if ((msg.content.method == + mtx::events::msg::VerificationMethods::SASv1) && + (msg.content.key_agreement_protocol == "curve25519-hkdf-sha256") && + (msg.content.hash == "sha256") && + ((msg.content.message_authentication_code == "hkdf-hmac-sha256") || + (msg.content.message_authentication_code == "hmac-sha256"))) { + this->commitment = msg.content.commitment; + if (std::find(msg.content.short_authentication_string.begin(), + msg.content.short_authentication_string.end(), + mtx::events::msg::SASMethods::Emoji) != + msg.content.short_authentication_string.end()) { + this->method = DeviceVerificationFlow::Method::Emoji; + } else { + this->method = DeviceVerificationFlow::Method::Decimal; + } + this->mac_method = msg.content.message_authentication_code; + this->sendVerificationKey(); + } else { + this->cancelVerification(); + } + } + }); connect(ChatPage::instance(), &ChatPage::recievedDeviceVerificationCancel, this, @@ -48,19 +96,55 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) auto msg = std::get>(message); if (msg.content.transaction_id == this->transaction_id) { - std::cout << "Recieved Event Cancel" << std::endl; - } - }); - connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationKey, - this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - std::cout << "Recieved Event Key" << std::endl; + emit verificationCanceled(); } }); + connect( + ChatPage::instance(), + &ChatPage::recievedDeviceVerificationKey, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + this->sas->set_their_key(msg.content.key); + std::string info; + if (this->sender == true) { + info = "MATRIX_KEY_VERIFICATION_SAS|" + + http::client()->user_id().to_string() + "|" + + http::client()->device_id() + "|" + + this->sas->public_key() + "|" + + this->toClient.to_string() + "|" + + this->deviceId.toStdString() + "|" + msg.content.key + + "|" + this->transaction_id; + } else { + info = "MATRIX_KEY_VERIFICATION_SAS|" + + this->toClient.to_string() + "|" + + this->deviceId.toStdString() + "|" + msg.content.key + + "|" + http::client()->user_id().to_string() + "|" + + http::client()->device_id() + "|" + + this->sas->public_key() + "|" + this->transaction_id; + } + + if (this->method == DeviceVerificationFlow::Method::Emoji) { + this->sasList = this->sas->generate_bytes_emoji(info); + } else if (this->method == DeviceVerificationFlow::Method::Decimal) { + this->sasList = this->sas->generate_bytes_decimal(info); + } + if (this->sender == false) { + emit this->verificationRequestAccepted(this->method); + this->sendVerificationKey(); + } else { + if (this->commitment == + mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256( + msg.content.key + + this->canonical_json["content"].dump()))) { + emit this->verificationRequestAccepted(this->method); + } else { + this->cancelVerification(); + } + } + } + }); connect(ChatPage::instance(), &ChatPage::recievedDeviceVerificationMac, this, @@ -104,6 +188,12 @@ DeviceVerificationFlow::getSender() return this->sender; } +std::vector +DeviceVerificationFlow::getSasList() +{ + return this->sasList; +} + void DeviceVerificationFlow::setTransactionId(QString transaction_id_) { @@ -133,6 +223,8 @@ void DeviceVerificationFlow::setSender(bool sender_) { this->sender = sender_; + if (this->sender == true) + this->transaction_id = http::client()->generate_txn_id(); } //! accepts a verification @@ -147,23 +239,26 @@ DeviceVerificationFlow::acceptVerificationRequest() req.key_agreement_protocol = "curve25519-hkdf-sha256"; req.hash = "sha256"; req.message_authentication_code = "hkdf-hmac-sha256"; - req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, - mtx::events::msg::SASMethods::Emoji}; - req.commitment = ""; - - emit this->verificationRequestAccepted(this->method); + if (this->method == DeviceVerificationFlow::Method::Emoji) + req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji}; + else if (this->method == DeviceVerificationFlow::Method::Decimal) + req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal}; + req.commitment = mtx::crypto::bin2base64_unpadded( + mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump())); body[this->toClient][this->deviceId.toStdString()] = req; + std::cout << "Accepting the Verification" << std::endl; + std::cout << json(body) << std::endl; + http::client() ->send_to_device( - this->transaction_id, body, [this](mtx::http::RequestErr err) { + this->transaction_id, body, [](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to accept verification request: {} {}", err->matrix_error.error, static_cast(err->status_code)); - emit this->verificationRequestAccepted(rand() % 2 ? Emoji : Decimal); }); } //! starts the verification flow @@ -183,6 +278,7 @@ DeviceVerificationFlow::startVerificationRequest() mtx::events::msg::SASMethods::Emoji}; body[this->toClient][this->deviceId.toStdString()] = req; + this->canonical_json = nlohmann::json(req); http::client() ->send_to_devicewarn("failed to start verification request: {} {}", err->matrix_error.error, static_cast(err->status_code)); - std::cout << nlohmann::json(body).dump(2) << std::endl; }); } //! sends a verification request @@ -236,6 +331,8 @@ DeviceVerificationFlow::cancelVerification() body[this->toClient][deviceId.toStdString()] = req; + emit this->verificationCanceled(); + http::client() ->send_to_device( @@ -254,7 +351,7 @@ DeviceVerificationFlow::sendVerificationKey() mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationKey req; - req.key = ""; + req.key = this->sas->public_key(); req.transaction_id = this->transaction_id; body[this->toClient][deviceId.toStdString()] = req; diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index b651394..bddf8ed 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -7,6 +7,8 @@ class QTimer; +using sas_ptr = std::unique_ptr; + class DeviceVerificationFlow : public QObject { Q_OBJECT @@ -16,6 +18,7 @@ class DeviceVerificationFlow : public QObject Q_PROPERTY(QString userId READ getUserId WRITE setUserId) Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId) Q_PROPERTY(Method method READ getMethod WRITE setMethod) + Q_PROPERTY(std::vector sasList READ getSasList) public: enum Method @@ -30,6 +33,7 @@ public: QString getUserId(); QString getDeviceId(); Method getMethod(); + std::vector getSasList(); void setTransactionId(QString transaction_id_); bool getSender(); void setUserId(QString userID); @@ -37,6 +41,8 @@ public: void setMethod(Method method_); void setSender(bool sender_); + nlohmann::json canonical_json; + public slots: //! sends a verification request void sendVerificationRequest(); @@ -66,6 +72,10 @@ private: bool sender; QTimer *timeout = nullptr; + sas_ptr sas; + std::string mac_method; std::string transaction_id; + std::string commitment; mtx::identifiers::User toClient; + std::vector sasList; }; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 22fe4d6..aaefaed 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -170,32 +170,44 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin &ChatPage::decryptSidebarChanged, this, &TimelineViewManager::updateEncryptedDescriptions); - connect(dynamic_cast(parent), - &ChatPage::recievedDeviceVerificationRequest, - this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - QString tranID = QString::fromStdString(msg.content.transaction_id); - QString deviceId = QString::fromStdString(msg.content.from_device); - QString userId = QString::fromStdString(msg.sender); - if (!(this->dvList->exist(tranID))) { - emit newDeviceVerificationRequest(tranID, userId, deviceId); - } - }); - connect(dynamic_cast(parent), - &ChatPage::recievedDeviceVerificationStart, - this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - QString tranID = QString::fromStdString(msg.content.transaction_id); - QString deviceId = QString::fromStdString(msg.content.from_device); - QString userId = QString::fromStdString(msg.sender); - if (!(this->dvList->exist(tranID))) { - emit newDeviceVerificationRequest(tranID, userId, deviceId); - } - }); + connect( + dynamic_cast(parent), + &ChatPage::recievedDeviceVerificationRequest, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + auto flow = new DeviceVerificationFlow(this); + if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) { + if (std::find(msg.content.methods.begin(), + msg.content.methods.end(), + mtx::events::msg::VerificationMethods::SASv1) != + msg.content.methods.end()) { + emit newDeviceVerificationRequest( + std::move(flow), + QString::fromStdString(msg.content.transaction_id), + QString::fromStdString(msg.sender), + QString::fromStdString(msg.content.from_device)); + } + } + }); + connect( + dynamic_cast(parent), + &ChatPage::recievedDeviceVerificationStart, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + auto flow = new DeviceVerificationFlow(this); + flow->canonical_json = nlohmann::json(msg.content); + if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) { + emit newDeviceVerificationRequest( + std::move(flow), + QString::fromStdString(msg.content.transaction_id), + QString::fromStdString(msg.sender), + QString::fromStdString(msg.content.from_device)); + } + }); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 7091271..946461f 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -67,7 +67,10 @@ signals: void initialSyncChanged(bool isInitialSync); void replyingEventChanged(QString replyingEvent); void replyClosed(); - void newDeviceVerificationRequest(QString transactionId, QString userId, QString deviceId); + void newDeviceVerificationRequest(DeviceVerificationFlow *flow, + QString transactionId, + QString userId, + QString deviceId); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); From ce013e67a63edf10b0b0357cd1318ce622a74e97 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Tue, 23 Jun 2020 03:35:56 +0530 Subject: [PATCH 15/70] Add some more slots and mac --- .../DeviceVerification.qml | 4 +- src/ChatPage.h | 2 + src/DeviceVerificationFlow.cpp | 195 ++++++++++++++---- src/DeviceVerificationFlow.h | 6 + src/Olm.cpp | 11 +- src/timeline/TimelineViewManager.cpp | 1 + 6 files changed, 172 insertions(+), 47 deletions(-) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 316fbe4..1a3d143 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -240,7 +240,7 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "They match." - onClicked: { stack.replace(awaitingVerificationConfirmation); flow.acceptDevice(); } + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } } } } @@ -379,7 +379,7 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "They match." - onClicked: { stack.replace(awaitingVerificationConfirmation); flow.acceptDevice(); } + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } } } } diff --git a/src/ChatPage.h b/src/ChatPage.h index b05a388..172f74f 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -175,6 +175,8 @@ signals: void recievedDeviceVerificationKey(const mtx::events::collections::DeviceEvents &message); void recievedDeviceVerificationMac(const mtx::events::collections::DeviceEvents &message); void recievedDeviceVerificationStart(const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationReady(const mtx::events::collections::DeviceEvents &message); + void recievedDeviceVerificationDone(const mtx::events::collections::DeviceEvents &message); private slots: void showUnreadMessageNotification(int count); diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 607cc27..0de94c5 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -1,11 +1,10 @@ #include "DeviceVerificationFlow.h" #include "ChatPage.h" #include "Logging.h" +#include "Utils.h" #include -#include // only for debugging #include -#include // only for debugging static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes @@ -28,32 +27,27 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) auto msg = std::get>(message); if (msg.content.transaction_id == this->transaction_id) { - if (std::find(msg.content.key_agreement_protocols.begin(), - msg.content.key_agreement_protocols.end(), - "curve25519-hkdf-sha256") != - msg.content.key_agreement_protocols.end() && - std::find(msg.content.hashes.begin(), - msg.content.hashes.end(), - "sha256") != msg.content.hashes.end() && + if ((std::find(msg.content.key_agreement_protocols.begin(), + msg.content.key_agreement_protocols.end(), + "curve25519-hkdf-sha256") != + msg.content.key_agreement_protocols.end()) && + (std::find(msg.content.hashes.begin(), + msg.content.hashes.end(), + "sha256") != msg.content.hashes.end()) && (std::find(msg.content.message_authentication_codes.begin(), msg.content.message_authentication_codes.end(), "hmac-sha256") != - msg.content.message_authentication_codes.end() || - std::find(msg.content.message_authentication_codes.begin(), - msg.content.message_authentication_codes.end(), - "hkdf-hmac-sha256") != - msg.content.message_authentication_codes.end()) && - (std::find(msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), - mtx::events::msg::SASMethods::Decimal) != - msg.content.short_authentication_string.end() || - std::find(msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.content.short_authentication_string.end())) { - this->sendVerificationKey(); // Not sure about this maybe - // those optional methods - this->canonical_json = nlohmann::json(msg); + msg.content.message_authentication_codes.end()) && + ((std::find(msg.content.short_authentication_string.begin(), + msg.content.short_authentication_string.end(), + mtx::events::msg::SASMethods::Decimal) != + msg.content.short_authentication_string.end()) || + (std::find(msg.content.short_authentication_string.begin(), + msg.content.short_authentication_string.end(), + mtx::events::msg::SASMethods::Emoji) != + msg.content.short_authentication_string.end()))) { + this->acceptVerificationRequest(); + this->canonical_json = nlohmann::json(msg.content); } else { this->cancelVerification(); } @@ -67,12 +61,9 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) auto msg = std::get>(message); if (msg.content.transaction_id == this->transaction_id) { - if ((msg.content.method == - mtx::events::msg::VerificationMethods::SASv1) && - (msg.content.key_agreement_protocol == "curve25519-hkdf-sha256") && + if ((msg.content.key_agreement_protocol == "curve25519-hkdf-sha256") && (msg.content.hash == "sha256") && - ((msg.content.message_authentication_code == "hkdf-hmac-sha256") || - (msg.content.message_authentication_code == "hmac-sha256"))) { + (msg.content.message_authentication_code == "hkdf-hmac-sha256")) { this->commitment = msg.content.commitment; if (std::find(msg.content.short_authentication_string.begin(), msg.content.short_authentication_string.end(), @@ -136,8 +127,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) } else { if (this->commitment == mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256( - msg.content.key + - this->canonical_json["content"].dump()))) { + msg.content.key + this->canonical_json.dump()))) { emit this->verificationRequestAccepted(this->method); } else { this->cancelVerification(); @@ -152,7 +142,51 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) auto msg = std::get>(message); if (msg.content.transaction_id == this->transaction_id) { - std::cout << "Recieved Event Mac" << std::endl; + std::string info = + "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() + + this->deviceId.toStdString() + + http::client()->user_id().to_string() + + http::client()->device_id() + this->transaction_id; + + std::vector key_list; + std::string key_string; + for (auto mac : msg.content.mac) { + if (mac.second == + this->sas->calculate_mac(this->device_keys[mac.first], + info + mac.first)) { + key_string += mac.first; + } else { + this->cancelVerification(); + return; + } + } + if (msg.content.keys == + this->sas->calculate_mac(key_string, info + "KEY_IDS")) { + this->sendVerificationDone(); + } else { + this->cancelVerification(); + } + } + }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationReady, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + this->startVerificationRequest(); + } + }); + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationDone, + this, + [this](const mtx::events::collections::DeviceEvents &message) { + auto msg = + std::get>(message); + if (msg.content.transaction_id == this->transaction_id) { + this->startVerificationRequest(); + emit this->deviceVerified(); } }); timeout->start(TIMEOUT); @@ -205,6 +239,34 @@ DeviceVerificationFlow::setUserId(QString userID) { this->userId = userID; this->toClient = mtx::identifiers::parse(userID.toStdString()); + + mtx::responses::QueryKeys res; + mtx::requests::QueryKeys req; + req.device_keys[userID.toStdString()] = {}; + http::client()->query_keys( + req, + [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + + for (auto x : res.device_keys) { + for (auto y : x.second) { + auto z = y.second; + if (z.user_id == user_id && + z.device_id == this->deviceId.toStdString()) { + for (auto a : z.keys) { + // TODO: Verify Signatures + this->device_keys[a.first] = a.second; + } + } + } + } + }); } void @@ -248,9 +310,6 @@ DeviceVerificationFlow::acceptVerificationRequest() body[this->toClient][this->deviceId.toStdString()] = req; - std::cout << "Accepting the Verification" << std::endl; - std::cout << json(body) << std::endl; - http::client() ->send_to_device( @@ -261,6 +320,50 @@ DeviceVerificationFlow::acceptVerificationRequest() static_cast(err->status_code)); }); } +//! responds verification request +void +DeviceVerificationFlow::sendVerificationReady() +{ + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationReady req; + + req.from_device = http::client()->device_id(); + req.transaction_id = this->transaction_id; + req.methods = {mtx::events::msg::VerificationMethods::SASv1}; + + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification ready: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); +} +//! accepts a verification +void +DeviceVerificationFlow::sendVerificationDone() +{ + mtx::requests::ToDeviceMessages body; + mtx::events::msg::KeyVerificationDone req; + + req.transaction_id = this->transaction_id; + + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification done: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); +} //! starts the verification flow void DeviceVerificationFlow::startVerificationRequest() @@ -373,9 +476,25 @@ DeviceVerificationFlow::sendVerificationMac() mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationMac req; + std::string info = "MATRIX_KEY_VERIFICATION_MAC" + http::client()->user_id().to_string() + + http::client()->device_id() + this->toClient.to_string() + + this->deviceId.toStdString() + this->transaction_id; + req.transaction_id = this->transaction_id; - // req.mac = ""; - req.keys = ""; + //! this vector stores the type of the key and the key + std::vector> key_list; + key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519)); + std::sort(key_list.begin(), key_list.end()); + for (auto x : key_list) { + req.mac.insert( + std::make_pair(x.first + ":" + http::client()->device_id(), + this->sas->calculate_mac( + x.second, info + x.first + ":" + http::client()->device_id()))); + req.keys += x.first + ":" + http::client()->device_id() + ","; + } + + req.keys = + this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS"); body[this->toClient][deviceId.toStdString()] = req; diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index bddf8ed..81ab9c9 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -2,6 +2,7 @@ #include "Olm.h" +#include "mtx/responses/crypto.hpp" #include #include @@ -46,6 +47,10 @@ public: public slots: //! sends a verification request void sendVerificationRequest(); + //! accepts a verification request + void sendVerificationReady(); + //! completes the verification flow(); + void sendVerificationDone(); //! accepts a verification void acceptVerificationRequest(); //! starts the verification flow @@ -78,4 +83,5 @@ private: std::string commitment; mtx::identifiers::User toClient; std::vector sasList; + std::map device_keys; }; diff --git a/src/Olm.cpp b/src/Olm.cpp index 6c1d3fd..7d7037c 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -9,7 +9,6 @@ #include "MatrixClient.h" #include "Utils.h" #include -#include // only for debugging static const std::string STORAGE_SECRET_KEY("secret"); constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2"; @@ -79,22 +78,20 @@ handle_to_device_messages(const std::vectorrecievedDeviceVerificationAccept(msg); - std::cout << j_msg.dump(2) << std::endl; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { ChatPage::instance()->recievedDeviceVerificationRequest(msg); - std::cout << j_msg.dump(2) << std::endl; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { ChatPage::instance()->recievedDeviceVerificationCancel(msg); - std::cout << j_msg.dump(2) << std::endl; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { ChatPage::instance()->recievedDeviceVerificationKey(msg); - std::cout << j_msg.dump(2) << std::endl; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { ChatPage::instance()->recievedDeviceVerificationMac(msg); - std::cout << j_msg.dump(2) << std::endl; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { ChatPage::instance()->recievedDeviceVerificationStart(msg); - std::cout << j_msg.dump(2) << std::endl; + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { + ChatPage::instance()->recievedDeviceVerificationReady(msg); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { + ChatPage::instance()->recievedDeviceVerificationDone(msg); } else { nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index aaefaed..7d016cb 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -183,6 +183,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin msg.content.methods.end(), mtx::events::msg::VerificationMethods::SASv1) != msg.content.methods.end()) { + flow->sendVerificationReady(); emit newDeviceVerificationRequest( std::move(flow), QString::fromStdString(msg.content.transaction_id), From d49ab156569f4f963a2306ff8074b6a22f28a31c Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Tue, 23 Jun 2020 23:29:00 +0530 Subject: [PATCH 16/70] Some Improvements - DeviceVerificationList change to LinkedList to improve time complexity while deleting - Downgrade the flow to not use key.verification.done and key.verification.ready --- resources/qml/TimelineView.qml | 1 + .../DeviceVerification.qml | 26 +++++++++++-------- src/DeviceVerificationFlow.cpp | 6 +++-- src/timeline/TimelineViewManager.cpp | 17 +++--------- src/timeline/TimelineViewManager.h | 3 ++- 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 99d1468..7ece08e 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -110,6 +110,7 @@ Page { flow.sender = false; flow.deviceId = deviceId; flow.tranId = transactionId; + deviceVerificationList.add(flow.tranId); var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow,sender: false}); dialog.show(); diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 1a3d143..03fc505 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -30,7 +30,11 @@ ApplicationWindow { implicitHeight: currentItem.implicitHeight } - onClosing: stack.replace(newVerificationRequest) + onClosing: { + flow.cancelVerification(); + deviceVerificationList.remove(flow.tranId); + delete flow; + } property var flow Connections { @@ -78,7 +82,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -135,7 +139,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -179,7 +183,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -230,7 +234,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -369,7 +373,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -413,7 +417,7 @@ ApplicationWindow { onClicked: { dialog.close(); flow.cancelVerification(); - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -451,7 +455,7 @@ ApplicationWindow { text: "Close" onClicked: { dialog.close() - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -485,8 +489,8 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight text: "Close" onClicked: { - dialog.close() - // deviceVerificationList.remove(flow.tranId); + dialog.close(); + deviceVerificationList.remove(flow.tranId); delete flow; } } @@ -521,7 +525,7 @@ ApplicationWindow { text: "Close" onClicked: { dialog.close() - // deviceVerificationList.remove(flow.tranId); + deviceVerificationList.remove(flow.tranId); delete flow; } } diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 0de94c5..db76aeb 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -1,7 +1,6 @@ #include "DeviceVerificationFlow.h" #include "ChatPage.h" #include "Logging.h" -#include "Utils.h" #include #include @@ -162,7 +161,10 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) } if (msg.content.keys == this->sas->calculate_mac(key_string, info + "KEY_IDS")) { - this->sendVerificationDone(); + // uncomment this in future to be compatible with the + // MSC2366 this->sendVerificationDone(); and remoeve the + // below line + emit this->deviceVerified(); } else { this->cancelVerification(); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 7d016cb..234b0bb 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -26,26 +26,17 @@ namespace msgs = mtx::events::msg; void DeviceVerificationList::add(QString tran_id) { - this->dv_list.push_back(tran_id); + this->dv_list.append(tran_id); } void DeviceVerificationList::remove(QString tran_id) { - for (QVector::iterator it = 0; it != (this->dv_list).end(); ++it) { - if (*it == tran_id) { - this->dv_list.erase(it); - break; - } - } + this->dv_list.removeOne(tran_id); } bool DeviceVerificationList::exist(QString tran_id) { - for (int i = 0; i < (this->dv_list).size(); ++i) { - if (dv_list[i] == tran_id) - return true; - } - return false; + return this->dv_list.contains(tran_id); } void @@ -183,7 +174,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin msg.content.methods.end(), mtx::events::msg::VerificationMethods::SASv1) != msg.content.methods.end()) { - flow->sendVerificationReady(); + // flow->sendVerificationReady(); emit newDeviceVerificationRequest( std::move(flow), QString::fromStdString(msg.content.transaction_id), diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 946461f..8af6d13 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -31,7 +32,7 @@ public: Q_INVOKABLE bool exist(QString tran_id); private: - QVector dv_list; + QLinkedList dv_list; }; class TimelineViewManager : public QObject From 1633650303e2129ae1e255f4e17a0fbff13638b1 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Wed, 24 Jun 2020 23:05:32 +0530 Subject: [PATCH 17/70] Some more changes - remove unnecessary field sender in userprofile.qml - cover user facing string with qsTr to get picked by translations - add spacing and fix theming issue - increase and add color to username - change back to QVector from QLinkedList cause I have mistaken better time complexity to give better benchmark red --- resources/qml/TimelineView.qml | 2 +- resources/qml/UserProfile.qml | 36 ++- .../DeviceVerification.qml | 209 ++++++++++++++---- src/timeline/TimelineViewManager.cpp | 6 +- src/timeline/TimelineViewManager.h | 3 +- 5 files changed, 193 insertions(+), 63 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 7ece08e..e52d588 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -112,7 +112,7 @@ Page { flow.tranId = transactionId; deviceVerificationList.add(flow.tranId); var dialog = deviceVerificationDialog.createObject(timelineRoot, - {flow: flow,sender: false}); + {flow: flow}); dialog.show(); } } diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 80415a2..f060b0e 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -13,7 +13,7 @@ ApplicationWindow{ id:userProfileDialog height: 500 - width: 500 + width: 400 modality:Qt.WindowModal Layout.alignment: Qt.AlignHCenter palette: colors @@ -64,6 +64,9 @@ ApplicationWindow{ id: userProfileName text: user_data.userName fontSizeMode: Text.HorizontalFit + font.pixelSize: 16 + color:timelineManager.userColor(modelData.userId, colors.window) + font.bold: true Layout.alignment: Qt.AlignHCenter } @@ -71,6 +74,8 @@ ApplicationWindow{ id: matrixUserID text: user_data.userId fontSizeMode: Text.HorizontalFit + font.pixelSize: 16 + color:colors.text Layout.alignment: Qt.AlignHCenter } @@ -79,8 +84,6 @@ ApplicationWindow{ implicitWidth: userProfileDialog.width-20 clip: true Layout.alignment: Qt.AlignHCenter - ScrollBar.horizontal.policy: ScrollBar.AlwaysOn - ScrollBar.vertical.policy: ScrollBar.AlwaysOn ListView{ id: deviceList @@ -98,6 +101,7 @@ ApplicationWindow{ Text{ Layout.fillWidth: true color: colors.text + font.bold: true Layout.alignment: Qt.AlignRight text: deviceID } @@ -116,12 +120,15 @@ ApplicationWindow{ {userId : user_data.userId,sender: true,deviceId : model.deviceID}); deviceVerificationList.add(newFlow.tranId); var dialog = deviceVerificationDialog.createObject(userProfileDialog, - {flow: newFlow,sender: true}); + {flow: newFlow}); dialog.show(); } + palette { + button: "white" + } contentItem: Text { text: verifyButton.text - color: colors.background + color: "black" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } @@ -134,19 +141,26 @@ ApplicationWindow{ id: okbutton text:"OK" onClicked: userProfileDialog.close() - anchors.margins: { - right:10 - bottom:10 + anchors { + right: parent.right + bottom: parent.bottom + } + + anchors.margins : { + right : 10 + bottom : 10 + } + + palette { + button: "white" } contentItem: Text { text: okbutton.text - color: colors.background + color: "black" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } - - Layout.alignment: Qt.AlignRight | Qt.AlignBottom } } diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 03fc505..12e38f2 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -7,7 +7,6 @@ import Qt.labs.settings 1.0 import im.nheko 1.0 ApplicationWindow { - property bool sender: true title: stack.currentItem.title id: dialog @@ -25,7 +24,7 @@ ApplicationWindow { width: stack.implicitWidth StackView { id: stack - initialItem: sender == true?newVerificationRequest:acceptNewVerificationRequest + initialItem: flow.sender == true?newVerificationRequest:acceptNewVerificationRequest implicitWidth: currentItem.implicitWidth implicitHeight: currentItem.implicitHeight } @@ -52,7 +51,7 @@ ApplicationWindow { Component { id: newVerificationRequest Pane { - property string title: "Sending Device Verification Request" + property string title: qsTr("Sending Device Verification Request") ColumnLayout { spacing: 16 Label { @@ -60,8 +59,8 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.Wrap - text: "A new device was added." - + text: qsTr("A new device was added.") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -70,15 +69,24 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.Wrap - text: "The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device." - + text: qsTr("The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device.") + color:colors.text verticalAlignment: Text.AlignVCenter } RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: "Cancel" + text: qsTr("Cancel") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); flow.cancelVerification(); @@ -91,7 +99,16 @@ ApplicationWindow { } Button { Layout.alignment: Qt.AlignRight - text: "Start verification" + text: qsTr("Start verification") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.startVerificationRequest(); } } } @@ -102,7 +119,7 @@ ApplicationWindow { Component { id: acceptNewVerificationRequest Pane { - property string title: "Recieving Device Verification Request" + property string title: qsTr("Recieving Device Verification Request") ColumnLayout { spacing: 16 @@ -111,15 +128,15 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.Wrap - text: "The device was requested to be verified" - + text: qsTr("The device was requested to be verified") + color:colors.text verticalAlignment: Text.AlignVCenter } RowLayout { RadioButton { Layout.alignment: Qt.AlignLeft - text: "Decimal" + text: qsTr("Decimal") onClicked: { flow.method = DeviceVerificationFlow.Decimal } } Item { @@ -127,7 +144,7 @@ ApplicationWindow { } RadioButton { Layout.alignment: Qt.AlignRight - text: "Emoji" + text: qsTr("Emoji") onClicked: { flow.method = DeviceVerificationFlow.Emoji } } } @@ -135,7 +152,16 @@ ApplicationWindow { RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: "Deny" + text: qsTr("Deny") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); flow.cancelVerification(); @@ -148,7 +174,16 @@ ApplicationWindow { } Button { Layout.alignment: Qt.AlignRight - text: "Accept" + text: qsTr("Accept") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.acceptVerificationRequest(); } } } @@ -159,7 +194,7 @@ ApplicationWindow { Component { id: awaitingVerificationRequestAccept Pane { - property string title: "Waiting for other party" + property string title: qsTr("Waiting for other party") ColumnLayout { spacing: 16 Label { @@ -168,8 +203,8 @@ ApplicationWindow { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: "Waiting for other side to accept the verification request." - + text: qsTr("Waiting for other side to accept the verification request.") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -179,7 +214,16 @@ ApplicationWindow { RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: "Cancel" + text: qsTr("Cancel") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); flow.cancelVerification(); @@ -198,7 +242,7 @@ ApplicationWindow { Component { id: digitVerification Pane { - property string title: "Verification Code" + property string title: qsTr("Verification Code") ColumnLayout { spacing: 16 Label { @@ -206,8 +250,8 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.Wrap - text: "Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!" - + text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -230,7 +274,16 @@ ApplicationWindow { RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: "They do not match!" + text: qsTr("They do not match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); flow.cancelVerification(); @@ -243,7 +296,16 @@ ApplicationWindow { } Button { Layout.alignment: Qt.AlignRight - text: "They match." + text: qsTr("They match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } } } @@ -254,7 +316,7 @@ ApplicationWindow { Component { id: emojiVerification Pane { - property string title: "Verification Code" + property string title: qsTr("Verification Code") ColumnLayout { spacing: 16 Label { @@ -262,8 +324,8 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.Wrap - text: "Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!" - + text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -369,7 +431,16 @@ ApplicationWindow { RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: "They do not match!" + text: qsTr("They do not match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); flow.cancelVerification(); @@ -382,7 +453,16 @@ ApplicationWindow { } Button { Layout.alignment: Qt.AlignRight - text: "They match." + text: qsTr("They match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } } } @@ -393,7 +473,7 @@ ApplicationWindow { Component { id: awaitingVerificationConfirmation Pane { - property string title: "Awaiting Confirmation" + property string title: qsTr("Awaiting Confirmation") ColumnLayout { spacing: 16 Label { @@ -402,8 +482,8 @@ ApplicationWindow { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: "Waiting for other side to complete verification." - + text: qsTr("Waiting for other side to complete verification.") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -413,7 +493,16 @@ ApplicationWindow { RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: "Cancel" + text: qsTr("Cancel") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); flow.cancelVerification(); @@ -432,7 +521,7 @@ ApplicationWindow { Component { id: verificationSuccess Pane { - property string title: "Successful Verification" + property string title: qsTr("Successful Verification") ColumnLayout { spacing: 16 Label { @@ -441,8 +530,8 @@ ApplicationWindow { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: "Verification successful! Both sides verified their devices!" - + text: qsTr("Verification successful! Both sides verified their devices!") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -452,7 +541,16 @@ ApplicationWindow { } Button { Layout.alignment: Qt.AlignRight - text: "Close" + text: qsTr("Close") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close() deviceVerificationList.remove(flow.tranId); @@ -467,7 +565,7 @@ ApplicationWindow { Component { id: partnerAborted Pane { - property string title: "Verification aborted!" + property string title: qsTr("Verification aborted!") ColumnLayout { spacing: 16 Label { @@ -476,8 +574,8 @@ ApplicationWindow { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: "Verification canceled by the other party!" - + text: qsTr("Verification canceled by the other party!") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -487,7 +585,16 @@ ApplicationWindow { } Button { Layout.alignment: Qt.AlignRight - text: "Close" + text: qsTr("Close") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } onClicked: { dialog.close(); deviceVerificationList.remove(flow.tranId); @@ -502,7 +609,7 @@ ApplicationWindow { Component { id: timedout Pane { - property string title: "Verification timed out" + property string title: qsTr("Verification timed out") ColumnLayout { spacing: 16 Text { @@ -511,8 +618,8 @@ ApplicationWindow { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: "Device verification timed out." - + text: qsTr("Device verification timed out.") + color:colors.text verticalAlignment: Text.AlignVCenter } @@ -521,8 +628,18 @@ ApplicationWindow { Layout.fillWidth: true } Button { + id: timedOutCancel Layout.alignment: Qt.AlignRight - text: "Close" + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + text: qsTr("Close") onClicked: { dialog.close() deviceVerificationList.remove(flow.tranId); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 234b0bb..a36a5bd 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -26,17 +26,17 @@ namespace msgs = mtx::events::msg; void DeviceVerificationList::add(QString tran_id) { - this->dv_list.append(tran_id); + this->deviceVerificationList.push_back(tran_id); } void DeviceVerificationList::remove(QString tran_id) { - this->dv_list.removeOne(tran_id); + this->deviceVerificationList.removeOne(tran_id); } bool DeviceVerificationList::exist(QString tran_id) { - return this->dv_list.contains(tran_id); + return this->deviceVerificationList.contains(tran_id); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 8af6d13..38aba02 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -32,7 +31,7 @@ public: Q_INVOKABLE bool exist(QString tran_id); private: - QLinkedList dv_list; + QVector deviceVerificationList; }; class TimelineViewManager : public QObject From 4862be06be6e32c1f4963feabfde010a9f64616c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 24 Jun 2020 16:24:22 +0200 Subject: [PATCH 18/70] Fix presence indicator --- resources/qml/Avatar.qml | 10 +++++---- resources/qml/MatrixText.qml | 6 ++++-- resources/qml/Reactions.qml | 13 +++++++++--- resources/qml/TimelineRow.qml | 12 +++++------ resources/qml/TimelineView.qml | 21 ++++++++++++------- resources/qml/UserProfile.qml | 3 ++- resources/qml/delegates/FileMessage.qml | 4 +++- resources/qml/delegates/ImageMessage.qml | 2 +- resources/qml/delegates/MessageDelegate.qml | 12 +++++------ .../qml/delegates/PlayableMediaMessage.qml | 4 ++-- resources/qml/delegates/Reply.qml | 4 +++- resources/qml/delegates/TextMessage.qml | 4 +++- .../DeviceVerification.qml | 9 +------- resources/qml/emoji/EmojiPicker.qml | 4 ++-- src/timeline/TimelineViewManager.cpp | 20 +++++++++++------- src/timeline/TimelineViewManager.h | 4 ++-- 16 files changed, 76 insertions(+), 56 deletions(-) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index f934e2f..e687e17 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -2,11 +2,13 @@ import QtQuick 2.6 import QtQuick.Controls 2.3 import QtGraphicalEffects 1.0 +import im.nheko 1.0 + Rectangle { id: avatar width: 48 height: 48 - radius: settings.avatarCircles ? height/2 : 3 + radius: Settings.avatarCircles ? height/2 : 3 property alias url: img.source property string userid @@ -40,7 +42,7 @@ Rectangle { anchors.fill: parent width: avatar.width height: avatar.height - radius: settings.avatarCircles ? height/2 : 3 + radius: Settings.avatarCircles ? height/2 : 3 } } @@ -52,8 +54,8 @@ Rectangle { height: avatar.height / 6 width: height - radius: settings.avatarCircles ? height / 2 : height / 4 - color: switch (timelineManager.userPresence(userid)) { + radius: Settings.avatarCircles ? height / 2 : height / 4 + color: switch (TimelineManager.userPresence(userid)) { case "online": return "#00cc66" case "unavailable": return "#ff9933" case "offline": // return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index cbb55be..d091004 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -1,6 +1,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 +import im.nheko 1.0 + TextEdit { textFormat: TextEdit.RichText readOnly: true @@ -10,10 +12,10 @@ TextEdit { onLinkActivated: { if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]) - else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) timelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]) + else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]) else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) { var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link) - timelineManager.setHistoryView(match[1]) + TimelineManager.setHistoryView(match[1]) chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain) } else Qt.openUrlExternally(link) diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index c109175..11109d7 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -1,6 +1,8 @@ import QtQuick 2.6 import QtQuick.Controls 2.2 +import im.nheko 1.0 + // This class is for showing Reactions in the timeline row, not for // adding new reactions via the emoji picker Flow { @@ -33,8 +35,13 @@ Flow { ToolTip.text: modelData.users onClicked: { +<<<<<<< HEAD console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + modelData.selfReactedEvent) timelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key) +======= + console.debug("Picked " + model.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + model.selfReactedEvent) + TimelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, model.key, model.selfReactedEvent) +>>>>>>> Fix presence indicator } @@ -46,7 +53,7 @@ Flow { TextMetrics { id: textMetrics - font.family: settings.emojiFont + font.family: Settings.emojiFont elide: Text.ElideRight elideWidth: 150 text: modelData.key @@ -55,8 +62,8 @@ Flow { Text { anchors.baseline: reactionCounter.baseline id: reactionText - text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…") - font.family: settings.emojiFont + text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…") + font.family: Settings.emojiFont color: reaction.hovered ? colors.highlight : colors.text maximumLineCount: 1 } diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index d1c2027..db58eb2 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -29,7 +29,7 @@ Item { } } Rectangle { - color: (settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent" + color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent" anchors.fill: row } RowLayout { @@ -48,8 +48,8 @@ Item { // fancy reply, if this is a reply Reply { visible: model.replyTo - modelData: chat.model.getDump(model.replyTo, model.id) - userColor: timelineManager.userColor(modelData.userId, colors.window) + modelData: chat.model.getDump(model.replyTo) + userColor: TimelineManager.userColor(modelData.userId, colors.window) } // actual message content @@ -84,7 +84,7 @@ Item { width: 16 } EmojiButton { - visible: settings.buttonsInTimeline + visible: Settings.buttonsInTimeline Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: 16 width: 16 @@ -96,7 +96,7 @@ Item { event_id: model.id } ImageButton { - visible: settings.buttonsInTimeline + visible: Settings.buttonsInTimeline Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: 16 width: 16 @@ -112,7 +112,7 @@ Item { onClicked: chat.model.replyAction(model.id) } ImageButton { - visible: settings.buttonsInTimeline + visible: Settings.buttonsInTimeline Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: 16 width: 16 diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e52d588..d9302ed 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -91,7 +91,7 @@ Page { visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker height: visible ? implicitHeight : 0 text: qsTr("Save as") - onTriggered: timelineManager.timeline.saveMedia(messageContextMenu.eventId) + onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId) } } @@ -104,7 +104,7 @@ Page { DeviceVerification {} } Connections { - target: timelineManager + target: TimelineManager onNewDeviceVerificationRequest: { flow.userId = userId; flow.sender = false; @@ -118,7 +118,7 @@ Page { } Label { - visible: !timelineManager.timeline && !timelineManager.isInitialSync + visible: !TimelineManager.timeline && !TimelineManager.isInitialSync anchors.centerIn: parent text: qsTr("No room open") font.pointSize: 24 @@ -128,7 +128,7 @@ Page { BusyIndicator { visible: running anchors.centerIn: parent - running: timelineManager.isInitialSync + running: TimelineManager.isInitialSync height: 200 width: 200 z: 3 @@ -137,7 +137,7 @@ Page { ListView { id: chat - visible: !!timelineManager.timeline + visible: TimelineManager.timeline != null cacheBuffer: 400 @@ -149,7 +149,7 @@ Page { anchors.leftMargin: 4 anchors.rightMargin: scrollbar.width - model: timelineManager.timeline + model: TimelineManager.timeline boundsBehavior: Flickable.StopAtBounds @@ -197,7 +197,7 @@ Page { onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom - property int delegateMaxWidth: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > 32) ? settings.timelineMaxWidth : (parent.width - 32) + property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > 32) ? Settings.timelineMaxWidth : (parent.width - 32) delegate: Rectangle { // This would normally be previousSection, but our model's order is inverted. @@ -303,7 +303,7 @@ Page { Label { id: userName text: chat.model.escapeEmoji(modelData.userName) - color: timelineManager.userColor(modelData.userId, colors.window) + color: TimelineManager.userColor(modelData.userId, colors.window) textFormat: Text.RichText MouseArea { @@ -381,8 +381,13 @@ Page { anchors.rightMargin: 20 anchors.bottom: parent.bottom +<<<<<<< HEAD modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {} userColor: timelineManager.userColor(modelData.userId, colors.window) +======= + modelData: chat.model ? chat.model.getDump(chat.model.reply) : {} + userColor: TimelineManager.userColor(modelData.userId, colors.window) +>>>>>>> Fix presence indicator } ImageButton { diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index f060b0e..a7ff8a3 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -57,6 +57,7 @@ ApplicationWindow{ height: 130 width: 130 displayName: modelData.userName + userid: modelData.userId Layout.alignment: Qt.AlignHCenter } @@ -65,7 +66,7 @@ ApplicationWindow{ text: user_data.userName fontSizeMode: Text.HorizontalFit font.pixelSize: 16 - color:timelineManager.userColor(modelData.userId, colors.window) + color:TimelineManager.userColor(modelData.userId, colors.window) font.bold: true Layout.alignment: Qt.AlignHCenter } diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index d8e4215..158daf4 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -1,6 +1,8 @@ import QtQuick 2.6 import QtQuick.Layouts 1.2 +import im.nheko 1.0 + Item { height: row.height + 24 width: parent ? parent.width : undefined @@ -29,7 +31,7 @@ Item { } MouseArea { anchors.fill: parent - onClicked: timelineManager.timeline.saveMedia(model.data.id) + onClicked: TimelineManager.timeline.saveMedia(model.data.id) cursorShape: Qt.PointingHandCursor } } diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 3885dda..b5c51c2 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -36,7 +36,7 @@ Item { MouseArea { enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready anchors.fill: parent - onClicked: timelineManager.openImageOverlay(model.data.url, model.data.id) + onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id) } } } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 9630ae3..6f69f02 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -37,7 +37,7 @@ Item { roleValue: MtxEvent.EmoteMessage NoticeMessage { formatted: chat.model.escapeEmoji(modelData.userName) + " " + model.data.formattedBody - color: timelineManager.userColor(modelData.userId, colors.window) + color: TimelineManager.userColor(modelData.userId, colors.window) } } DelegateChoice { @@ -100,31 +100,31 @@ Item { // TODO: make a more complex formatter for the power levels. roleValue: MtxEvent.PowerLevels NoticeMessage { - text: timelineManager.timeline.formatPowerLevelEvent(model.data.id) + text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id) } } DelegateChoice { roleValue: MtxEvent.RoomJoinRules NoticeMessage { - text: timelineManager.timeline.formatJoinRuleEvent(model.data.id) + text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id) } } DelegateChoice { roleValue: MtxEvent.RoomHistoryVisibility NoticeMessage { - text: timelineManager.timeline.formatHistoryVisibilityEvent(model.data.id) + text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id) } } DelegateChoice { roleValue: MtxEvent.RoomGuestAccess NoticeMessage { - text: timelineManager.timeline.formatGuestAccessEvent(model.data.id) + text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id) } } DelegateChoice { roleValue: MtxEvent.Member NoticeMessage { - text: timelineManager.timeline.formatMemberEvent(model.data.id); + text: TimelineManager.timeline.formatMemberEvent(model.data.id); } } DelegateChoice { diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 8d2fa8a..893325b 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -106,7 +106,7 @@ Rectangle { anchors.fill: parent onClicked: { switch (button.state) { - case "": timelineManager.timeline.cacheMedia(model.data.id); break; + case "": TimelineManager.timeline.cacheMedia(model.data.id); break; case "stopped": media.play(); console.log("play"); button.state = "playing" @@ -127,7 +127,7 @@ Rectangle { } Connections { - target: timelineManager.timeline + target: TimelineManager.timeline onMediaCached: { if (mxcUrl == model.data.url) { media.source = "file://" + cacheUrl diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index f9fd3f1..d4fffb0 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -3,6 +3,8 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 +import im.nheko 1.0 + Item { id: replyComponent @@ -26,7 +28,7 @@ Item { anchors.bottom: replyContainer.bottom width: 4 - color: timelineManager.userColor(reply.modelData.userId, colors.window) + color: TimelineManager.userColor(reply.modelData.userId, colors.window) } Column { diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index cc2d2da..99ff932 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -1,10 +1,12 @@ import ".." +import im.nheko 1.0 + MatrixText { property string formatted: model.data.formattedBody text: "" + formatted.replace("
", "
")
 	width: parent ? parent.width : undefined
 	height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
 	clip: true
-	font.pointSize: (settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? settings.fontSize * 3 : settings.fontSize
+	font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
 }
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 12e38f2..516bc74 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -2,7 +2,6 @@ import QtQuick 2.3
 import QtQuick.Controls 2.10
 import QtQuick.Window 2.2
 import QtQuick.Layouts 1.10
-import Qt.labs.settings 1.0
 
 import im.nheko 1.0
 
@@ -14,12 +13,6 @@ ApplicationWindow {
 
 	palette: colors
 
-	Settings {
-		id: settings
-		category: "user"
-		property bool emoji_font_family: true
-	}
-
 	height: stack.implicitHeight
 	width: stack.implicitWidth
 	StackView {
@@ -417,7 +410,7 @@ ApplicationWindow {
 									Layout.alignment: Qt.AlignHCenter
 									text: col.emoji.emoji
 									font.pixelSize: Qt.application.font.pixelSize * 2
-									font.family: settings.emoji_font_family
+									font.family: Settings.emojiFont
 								}
 								Label {
 									Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index f75221d..d2ca4da 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -73,7 +73,7 @@ Popup {
                 contentItem: Text {
                     horizontalAlignment: Text.AlignHCenter
                     verticalAlignment: Text.AlignVCenter
-                    font.family: settings.emojiFont
+                    font.family: Settings.emojiFont
                     
                     font.pixelSize: 36
                     text: model.unicode
@@ -104,7 +104,7 @@ Popup {
                 onClicked: {
                     console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id)
                     emojiPopup.close()
-                    timelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode)
+                    TimelineManager.queueReactionMessage(emojiPopup.room_id, emojiPopup.event_id, model.unicode)
                 }
             }
 
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index a36a5bd..14c6695 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -80,12 +80,16 @@ TimelineViewManager::userColor(QString id, QColor background)
         return userColors.value(id);
 }
 
-// QString
-// TimelineViewManager::userPresence(QString id) const
-// {
-//         return QString::fromStdString(
-//           mtx::presence::to_string(cache::presenceState(id.toStdString())));
-// }
+QString
+TimelineViewManager::userPresence(QString id) const
+{
+        if (id.isEmpty())
+                return "";
+        else
+                return QString::fromStdString(
+                  mtx::presence::to_string(cache::presenceState(id.toStdString())));
+}
+
 QString
 TimelineViewManager::userStatus(QString id) const
 {
@@ -110,6 +114,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
         qmlRegisterType("im.nheko", 1, 0, "DeviceVerificationFlow");
         qmlRegisterType("im.nheko", 1, 0, "UserProfileModel");
         qmlRegisterType("im.nheko", 1, 0, "UserProfileList");
+        qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", this);
+        qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", settings.data());
 
         qRegisterMetaType();
         qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel");
@@ -144,8 +150,6 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
         });
 #endif
         container->setMinimumSize(200, 200);
-        view->rootContext()->setContextProperty("timelineManager", this);
-        view->rootContext()->setContextProperty("settings", settings.data());
         view->rootContext()->setContextProperty("deviceVerificationList", this->dvList);
         updateColorPalette();
         view->engine()->addImageProvider("MxcImage", imgProvider);
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 38aba02..af8bc4b 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -57,7 +57,7 @@ public:
         Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
         Q_INVOKABLE QColor userColor(QString id, QColor background);
 
-        // Q_INVOKABLE QString userPresence(QString id) const;
+        Q_INVOKABLE QString userPresence(QString id) const;
         Q_INVOKABLE QString userStatus(QString id) const;
 
 signals:
@@ -131,4 +131,4 @@ private:
 
         DeviceVerificationList *dvList;
 };
-Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents)
\ No newline at end of file
+Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents)

From fd232b1f4a6d55b566ee138b30cfcc272129fabb Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Thu, 25 Jun 2020 22:38:48 +0530
Subject: [PATCH 19/70] Some more fixes

---
 resources/qml/UserProfile.qml  | 30 +++++++++++++++++-------------
 src/DeviceVerificationFlow.cpp | 12 ++++++++++--
 src/DeviceVerificationFlow.h   |  1 +
 3 files changed, 28 insertions(+), 15 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index a7ff8a3..e5d0162 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -13,7 +13,7 @@ ApplicationWindow{
 
     id:userProfileDialog
     height: 500
-    width: 400
+    width: 420
     modality:Qt.WindowModal
     Layout.alignment: Qt.AlignHCenter
     palette: colors
@@ -42,9 +42,8 @@ ApplicationWindow{
         id: userProfileItem
         width: userProfileDialog.width
         height: userProfileDialog.height
-        anchors.margins: {
-            top:20
-        }
+
+        Layout.fillHeight : true
 
         ColumnLayout{
             anchors.fill: userProfileItem
@@ -57,15 +56,18 @@ ApplicationWindow{
                 height: 130
                 width: 130
                 displayName: modelData.userName
-		userid: modelData.userId
+		        userid: modelData.userId
                 Layout.alignment: Qt.AlignHCenter
+                Layout.margins : {
+                    top: 10
+                }
             }
 
             Label{
                 id: userProfileName
                 text: user_data.userName
                 fontSizeMode: Text.HorizontalFit
-                font.pixelSize: 16
+                font.pixelSize: 20
                 color:TimelineManager.userColor(modelData.userId, colors.window)
                 font.bold: true
                 Layout.alignment: Qt.AlignHCenter
@@ -75,7 +77,7 @@ ApplicationWindow{
                 id: matrixUserID
                 text: user_data.userId
                 fontSizeMode: Text.HorizontalFit
-                font.pixelSize: 16
+                font.pixelSize: 15
                 color:colors.text
                 Layout.alignment: Qt.AlignHCenter
             }
@@ -90,7 +92,7 @@ ApplicationWindow{
                     id: deviceList
                     anchors.fill: parent
                     clip: true
-                    spacing: 10
+                    spacing: 4
 
                     model: UserProfileModel{
                         id: modelDeviceList
@@ -98,6 +100,9 @@ ApplicationWindow{
 
                     delegate: RowLayout{
                         width: parent.width
+                        Layout.margins : {
+                            top : 50
+                        }
                         ColumnLayout{
                             Text{
                                 Layout.fillWidth: true
@@ -124,6 +129,9 @@ ApplicationWindow{
                                     {flow: newFlow});
 				                dialog.show();
                             }
+                            Layout.margins:{
+                                right: 10
+                            }
                             palette {
                                 button: "white"
                             }
@@ -142,12 +150,8 @@ ApplicationWindow{
                 id: okbutton
                 text:"OK"
                 onClicked: userProfileDialog.close()
-                anchors {
-                    right: parent.right
-                    bottom: parent.bottom
-                }
 
-                anchors.margins : {
+                Layout.margins : {
                     right : 10
                     bottom : 10
                 }
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index db76aeb..9f120a0 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -164,7 +164,10 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                                         // uncomment this in future to be compatible with the
                                         // MSC2366 this->sendVerificationDone(); and remoeve the
                                         // below line
-                                        emit this->deviceVerified();
+                                        if (this->isMacVerified == true)
+                                                emit this->deviceVerified();
+                                        else
+                                                this->isMacVerified = true;
                                 } else {
                                         this->cancelVerification();
                                 }
@@ -503,11 +506,16 @@ DeviceVerificationFlow::sendVerificationMac()
         http::client()
           ->send_to_device(
-            this->transaction_id, body, [](mtx::http::RequestErr err) {
+            this->transaction_id, body, [this](mtx::http::RequestErr err) {
                     if (err)
                             nhlog::net()->warn("failed to send verification MAC: {} {}",
                                                err->matrix_error.error,
                                                static_cast(err->status_code));
+
+                    if (this->isMacVerified == true)
+                            emit this->deviceVerified();
+                    else
+                            this->isMacVerified = true;
             });
 }
 //! Completes the verification flow
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 81ab9c9..5830e70 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -78,6 +78,7 @@ private:
 
         QTimer *timeout = nullptr;
         sas_ptr sas;
+        bool isMacVerified;
         std::string mac_method;
         std::string transaction_id;
         std::string commitment;

From 75efa5d3a2586d16226526ddacf00ce3e8bd3367 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Thu, 25 Jun 2020 23:29:50 +0530
Subject: [PATCH 20/70] Fix the Weird auto-confirmation and cancellation

---
 src/DeviceVerificationFlow.cpp | 1 -
 src/DeviceVerificationFlow.h   | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 9f120a0..9b26089 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -190,7 +190,6 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                         auto msg =
                           std::get>(message);
                         if (msg.content.transaction_id == this->transaction_id) {
-                                this->startVerificationRequest();
                                 emit this->deviceVerified();
                         }
                 });
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 5830e70..ea86a10 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -78,7 +78,7 @@ private:
 
         QTimer *timeout = nullptr;
         sas_ptr sas;
-        bool isMacVerified;
+        bool isMacVerified = false;
         std::string mac_method;
         std::string transaction_id;
         std::string commitment;

From 00e36b6068786d64812135a4d505501134ebc214 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Fri, 26 Jun 2020 04:24:42 +0530
Subject: [PATCH 21/70] Add some Userprofile buttons

---
 resources/qml/UserProfile.qml                 | 62 +++++++++++++-
 .../DeviceVerification.qml                    | 15 +++-
 src/DeviceVerificationFlow.cpp                | 83 ++++++++++---------
 src/ui/UserProfile.cpp                        | 28 +++++++
 src/ui/UserProfile.h                          |  5 +-
 5 files changed, 146 insertions(+), 47 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index e5d0162..36c8586 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -12,7 +12,7 @@ ApplicationWindow{
     property var colors: currentActivePalette
 
     id:userProfileDialog
-    height: 500
+    height: 650
     width: 420
     modality:Qt.WindowModal
     Layout.alignment: Qt.AlignHCenter
@@ -43,7 +43,7 @@ ApplicationWindow{
         width: userProfileDialog.width
         height: userProfileDialog.height
 
-        Layout.fillHeight : true
+        // Layout.fillHeight : true
 
         ColumnLayout{
             anchors.fill: userProfileItem
@@ -82,8 +82,62 @@ ApplicationWindow{
                 Layout.alignment: Qt.AlignHCenter
             }
 
+            RowLayout{
+                Layout.alignment: Qt.AlignHCenter
+                ImageButton{
+                    image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
+                    Layout.margins: {
+                        left: 5
+                        right: 5
+                    }
+                    ToolTip.visible: hovered
+			        ToolTip.text: qsTr("Ban the user")
+                    onClicked : {
+                        userProfileList.banUser()
+                    }
+                }
+                // ImageButton{
+                //     image:":/icons/icons/ui/volume-off-indicator.png"
+                //     Layout.margins: {
+                //         left: 5
+                //         right: 5
+                //     }
+                //     ToolTip.visible: hovered
+			    //     ToolTip.text: qsTr("Ignore messages from this user")
+                //     onClicked : {
+                //         userProfileList.ignoreUser()
+                //     }
+                // }
+
+                ImageButton{
+                    image:":/icons/icons/ui/round-remove-button.png"
+                    Layout.margins: {
+                        left: 5
+                        right: 5
+                    }
+                    ToolTip.visible: hovered
+			        ToolTip.text: qsTr("Kick the user")
+                    onClicked : {
+                        userProfileList.kickUser()
+                    }
+                }
+
+                ImageButton{
+                    image:":/icons/icons/ui/black-bubble-speech.png"
+                    Layout.margins: {
+                        left: 5
+                        right: 5
+                    }
+                    ToolTip.visible: hovered
+			        ToolTip.text: qsTr("Start a conversation")
+                    onClicked : {
+                        userProfileList.startChat()
+                    }
+                }
+            }
+
             ScrollView {
-                implicitHeight: userProfileDialog.height/2+20
+                implicitHeight: userProfileDialog.height/2 + 20
                 implicitWidth: userProfileDialog.width-20
                 clip: true
                 Layout.alignment: Qt.AlignHCenter
@@ -150,6 +204,8 @@ ApplicationWindow{
                 id: okbutton
                 text:"OK"
                 onClicked: userProfileDialog.close()
+                
+                Layout.alignment: Qt.AlignRight
 
                 Layout.margins : {
                     right : 10
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 516bc74..ca21f48 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -82,8 +82,6 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close(); 
-							flow.cancelVerification();
-							deviceVerificationList.remove(flow.tranId);
 							delete flow; 
 						}
 					}
@@ -128,16 +126,26 @@ ApplicationWindow {
 
 				RowLayout {
 					RadioButton {
+						id: decimalRadio
 						Layout.alignment: Qt.AlignLeft
 						text: qsTr("Decimal")
+						contentItem: Text {
+    					    text: decimalRadio.text
+    					    color: colors.text
+    					}
 						onClicked: { flow.method = DeviceVerificationFlow.Decimal }
 					}
 					Item {
 						Layout.fillWidth: true
 					}
 					RadioButton {
+						id: emojiRadio
 						Layout.alignment: Qt.AlignRight
 						text: qsTr("Emoji")
+						contentItem: Text {
+    					    text: emojiRadio.text
+    					    color: colors.text
+    					}
 						onClicked: { flow.method = DeviceVerificationFlow.Emoji }
 					}
 				}
@@ -156,7 +164,7 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: { 
-							dialog.close(); 
+							dialog.close();
 							flow.cancelVerification();
 							deviceVerificationList.remove(flow.tranId);
 							delete flow; 
@@ -411,6 +419,7 @@ ApplicationWindow {
 									text: col.emoji.emoji
 									font.pixelSize: Qt.application.font.pixelSize * 2
 									font.family: Settings.emojiFont
+									color:colors.text
 								}
 								Label {
 									Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 9b26089..2c6e9c1 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -13,7 +13,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
 {
         timeout = new QTimer(this);
         timeout->setSingleShot(true);
-        this->sas = olm::client()->sas_init();
+        this->sas           = olm::client()->sas_init();
+        this->isMacVerified = false;
         connect(timeout, &QTimer::timeout, this, [this]() {
                 emit timedout();
                 this->deleteLater();
@@ -134,45 +135,47 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                           }
                   }
           });
-        connect(ChatPage::instance(),
-                &ChatPage::recievedDeviceVerificationMac,
-                this,
-                [this](const mtx::events::collections::DeviceEvents &message) {
-                        auto msg =
-                          std::get>(message);
-                        if (msg.content.transaction_id == this->transaction_id) {
-                                std::string info =
-                                  "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() +
-                                  this->deviceId.toStdString() +
-                                  http::client()->user_id().to_string() +
-                                  http::client()->device_id() + this->transaction_id;
-
-                                std::vector key_list;
-                                std::string key_string;
-                                for (auto mac : msg.content.mac) {
-                                        if (mac.second ==
-                                            this->sas->calculate_mac(this->device_keys[mac.first],
-                                                                     info + mac.first)) {
-                                                key_string += mac.first;
-                                        } else {
-                                                this->cancelVerification();
-                                                return;
-                                        }
-                                }
-                                if (msg.content.keys ==
-                                    this->sas->calculate_mac(key_string, info + "KEY_IDS")) {
-                                        // uncomment this in future to be compatible with the
-                                        // MSC2366 this->sendVerificationDone(); and remoeve the
-                                        // below line
-                                        if (this->isMacVerified == true)
-                                                emit this->deviceVerified();
-                                        else
-                                                this->isMacVerified = true;
-                                } else {
-                                        this->cancelVerification();
-                                }
-                        }
-                });
+        connect(
+          ChatPage::instance(),
+          &ChatPage::recievedDeviceVerificationMac,
+          this,
+          [this](const mtx::events::collections::DeviceEvents &message) {
+                  auto msg = std::get>(message);
+                  if (msg.content.transaction_id == this->transaction_id) {
+                          std::string info =
+                            "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() +
+                            this->deviceId.toStdString() + http::client()->user_id().to_string() +
+                            http::client()->device_id() + this->transaction_id;
+
+                          std::vector key_list;
+                          std::string key_string;
+                          for (auto mac : msg.content.mac) {
+                                  key_string += mac.first + ",";
+                                  if (device_keys[mac.first] != "") {
+                                          if (mac.second ==
+                                              this->sas->calculate_mac(this->device_keys[mac.first],
+                                                                       info + mac.first)) {
+                                          } else {
+                                                  this->cancelVerification();
+                                                  return;
+                                          }
+                                  }
+                          }
+                          key_string = key_string.substr(0, key_string.length() - 1);
+                          if (msg.content.keys ==
+                              this->sas->calculate_mac(key_string, info + "KEY_IDS")) {
+                                  // uncomment this in future to be compatible with the
+                                  // MSC2366 this->sendVerificationDone(); and remove the
+                                  // below line
+                                  if (this->isMacVerified == true)
+                                          emit this->deviceVerified();
+                                  else
+                                          this->isMacVerified = true;
+                          } else {
+                                  this->cancelVerification();
+                          }
+                  }
+          });
         connect(ChatPage::instance(),
                 &ChatPage::recievedDeviceVerificationReady,
                 this,
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 588d696..6aa4def 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -1,4 +1,5 @@
 #include "UserProfile.h"
+#include "ChatPage.h"
 #include "Logging.h"
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
@@ -86,3 +87,30 @@ UserProfile::updateDeviceList()
 {
         fetchDeviceList(this->userId);
 }
+
+void
+UserProfile::banUser()
+{
+        ChatPage::instance()->banUser(this->userId, "");
+}
+
+// void ignoreUser(){
+
+// }
+
+void
+UserProfile::kickUser()
+{
+        ChatPage::instance()->kickUser(this->userId, "");
+}
+
+void
+UserProfile::startChat()
+{
+        mtx::requests::CreateRoom req;
+        req.preset     = mtx::requests::Preset::PrivateChat;
+        req.visibility = mtx::requests::Visibility::Private;
+        if (utils::localUser() != this->userId)
+                req.invite = {this->userId.toStdString()};
+        emit ChatPage::instance()->createRoom(req);
+}
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index c37e23a..ad92d18 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -5,7 +5,6 @@
 #include 
 
 #include "MatrixClient.h"
-
 class DeviceInfo
 {
 public:
@@ -36,6 +35,10 @@ public:
 
         Q_INVOKABLE void fetchDeviceList(const QString &userID);
         Q_INVOKABLE void updateDeviceList();
+        Q_INVOKABLE void banUser();
+        // Q_INVOKABLE void ignoreUser();
+        Q_INVOKABLE void kickUser();
+        Q_INVOKABLE void startChat();
 
 signals:
         void userIdChanged();

From ffa61095b8be7c61c3b4cdd693e59239ab668516 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Fri, 26 Jun 2020 15:10:37 +0530
Subject: [PATCH 22/70] Error Handling and some fixes

---
 resources/qml/UserProfile.qml                 |  16 ++-
 .../DeviceVerification.qml                    |  52 ++-------
 src/DeviceVerificationFlow.cpp                | 109 +++++++++++-------
 src/DeviceVerificationFlow.h                  |  12 +-
 src/timeline/TimelineViewManager.cpp          |  45 +++++++-
 5 files changed, 138 insertions(+), 96 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 36c8586..6ef7503 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -108,30 +108,28 @@ ApplicationWindow{
                 //         userProfileList.ignoreUser()
                 //     }
                 // }
-
                 ImageButton{
-                    image:":/icons/icons/ui/round-remove-button.png"
+                    image:":/icons/icons/ui/black-bubble-speech.png"
                     Layout.margins: {
                         left: 5
                         right: 5
                     }
                     ToolTip.visible: hovered
-			        ToolTip.text: qsTr("Kick the user")
+			        ToolTip.text: qsTr("Start a private chat")
                     onClicked : {
-                        userProfileList.kickUser()
+                        userProfileList.startChat()
                     }
                 }
-
                 ImageButton{
-                    image:":/icons/icons/ui/black-bubble-speech.png"
+                    image:":/icons/icons/ui/round-remove-button.png"
                     Layout.margins: {
                         left: 5
                         right: 5
                     }
                     ToolTip.visible: hovered
-			        ToolTip.text: qsTr("Start a conversation")
+			        ToolTip.text: qsTr("Kick the user")
                     onClicked : {
-                        userProfileList.startChat()
+                        userProfileList.kickUser()
                     }
                 }
             }
@@ -205,7 +203,7 @@ ApplicationWindow{
                 text:"OK"
                 onClicked: userProfileDialog.close()
                 
-                Layout.alignment: Qt.AlignRight
+                Layout.alignment: Qt.AlignRight | Qt.AlignBottom
 
                 Layout.margins : {
                     right : 10
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index ca21f48..15c2d7a 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -22,12 +22,6 @@ ApplicationWindow {
 		implicitHeight: currentItem.implicitHeight
 	}
 
-	onClosing: {
-		flow.cancelVerification();
-		deviceVerificationList.remove(flow.tranId);
-		delete flow; 
-	}
-
 	property var flow
 	Connections {
 		target: flow
@@ -123,33 +117,6 @@ ApplicationWindow {
 					color:colors.text
 					verticalAlignment: Text.AlignVCenter
 				}
-
-				RowLayout {
-					RadioButton {
-						id: decimalRadio
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("Decimal")
-						contentItem: Text {
-    					    text: decimalRadio.text
-    					    color: colors.text
-    					}
-						onClicked: { flow.method = DeviceVerificationFlow.Decimal }
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-					RadioButton {
-						id: emojiRadio
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("Emoji")
-						contentItem: Text {
-    					    text: emojiRadio.text
-    					    color: colors.text
-    					}
-						onClicked: { flow.method = DeviceVerificationFlow.Emoji }
-					}
-				}
-
 				RowLayout {
 					Button {
 						Layout.alignment: Qt.AlignLeft
@@ -165,9 +132,8 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close();
-							flow.cancelVerification();
+							flow.cancelVerification(DeviceVerificationFlow.User);
 							deviceVerificationList.remove(flow.tranId);
-							delete flow; 
 						}
 					}
 					Item {
@@ -227,9 +193,8 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close(); 
-							flow.cancelVerification();
+							flow.cancelVerification(DeviceVerificationFlow.User);
 							deviceVerificationList.remove(flow.tranId);
-							delete flow; 
 						}
 					}
 					Item {
@@ -261,14 +226,17 @@ ApplicationWindow {
 					Label {
 						font.pixelSize: Qt.application.font.pixelSize * 2
 						text: flow.sasList[0]
+						color:colors.text
 					}
 					Label {
 						font.pixelSize: Qt.application.font.pixelSize * 2
 						text: flow.sasList[1]
+						color:colors.text
 					}
 					Label {
 						font.pixelSize: Qt.application.font.pixelSize * 2
 						text: flow.sasList[2]
+						color:colors.text
 					}
 				}
 
@@ -287,9 +255,8 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close(); 
-							flow.cancelVerification();
+							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
 							deviceVerificationList.remove(flow.tranId);
-							delete flow; 
 						}
 					}
 					Item {
@@ -411,6 +378,7 @@ ApplicationWindow {
 							implicitWidth: col.width
 							ColumnLayout {
 								id: col
+								Layout.fillWidth: true
 								anchors.bottom: parent.bottom
 								property var emoji: emojis.mapping[flow.sasList[index]]
 								Label {
@@ -424,6 +392,7 @@ ApplicationWindow {
 								Label {
 									Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
 									text: col.emoji.description
+									color:colors.text
 								}
 							}
 						}
@@ -445,9 +414,8 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close(); 
-							flow.cancelVerification();
+							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
 							deviceVerificationList.remove(flow.tranId);
-							delete flow; 
 						}
 					}
 					Item {
@@ -507,7 +475,7 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close(); 
-							flow.cancelVerification(); 
+							flow.cancelVerification(DeviceVerificationFlow.User); 
 							deviceVerificationList.remove(flow.tranId);
 							delete flow;
 						}
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 2c6e9c1..b5134a3 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -17,42 +17,53 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
         this->isMacVerified = false;
         connect(timeout, &QTimer::timeout, this, [this]() {
                 emit timedout();
+                this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
                 this->deleteLater();
         });
 
-        connect(ChatPage::instance(),
-                &ChatPage::recievedDeviceVerificationStart,
-                this,
-                [this](const mtx::events::collections::DeviceEvents &message) {
-                        auto msg =
-                          std::get>(message);
-                        if (msg.content.transaction_id == this->transaction_id) {
-                                if ((std::find(msg.content.key_agreement_protocols.begin(),
-                                               msg.content.key_agreement_protocols.end(),
-                                               "curve25519-hkdf-sha256") !=
-                                     msg.content.key_agreement_protocols.end()) &&
-                                    (std::find(msg.content.hashes.begin(),
-                                               msg.content.hashes.end(),
-                                               "sha256") != msg.content.hashes.end()) &&
-                                    (std::find(msg.content.message_authentication_codes.begin(),
-                                               msg.content.message_authentication_codes.end(),
-                                               "hmac-sha256") !=
-                                     msg.content.message_authentication_codes.end()) &&
-                                    ((std::find(msg.content.short_authentication_string.begin(),
+        connect(
+          ChatPage::instance(),
+          &ChatPage::recievedDeviceVerificationStart,
+          this,
+          [this](const mtx::events::collections::DeviceEvents &message) {
+                  auto msg =
+                    std::get>(message);
+                  if (msg.content.transaction_id == this->transaction_id) {
+                          if ((std::find(msg.content.key_agreement_protocols.begin(),
+                                         msg.content.key_agreement_protocols.end(),
+                                         "curve25519-hkdf-sha256") !=
+                               msg.content.key_agreement_protocols.end()) &&
+                              (std::find(msg.content.hashes.begin(),
+                                         msg.content.hashes.end(),
+                                         "sha256") != msg.content.hashes.end()) &&
+                              (std::find(msg.content.message_authentication_codes.begin(),
+                                         msg.content.message_authentication_codes.end(),
+                                         "hmac-sha256") !=
+                               msg.content.message_authentication_codes.end())) {
+                                  if (std::find(msg.content.short_authentication_string.begin(),
                                                 msg.content.short_authentication_string.end(),
                                                 mtx::events::msg::SASMethods::Decimal) !=
-                                      msg.content.short_authentication_string.end()) ||
-                                     (std::find(msg.content.short_authentication_string.begin(),
-                                                msg.content.short_authentication_string.end(),
-                                                mtx::events::msg::SASMethods::Emoji) !=
-                                      msg.content.short_authentication_string.end()))) {
-                                        this->acceptVerificationRequest();
-                                        this->canonical_json = nlohmann::json(msg.content);
-                                } else {
-                                        this->cancelVerification();
-                                }
-                        }
-                });
+                                      msg.content.short_authentication_string.end()) {
+                                          this->method = DeviceVerificationFlow::Method::Emoji;
+                                  } else if (std::find(
+                                               msg.content.short_authentication_string.begin(),
+                                               msg.content.short_authentication_string.end(),
+                                               mtx::events::msg::SASMethods::Emoji) !=
+                                             msg.content.short_authentication_string.end()) {
+                                          this->method = DeviceVerificationFlow::Method::Decimal;
+                                  } else {
+                                          this->cancelVerification(
+                                            DeviceVerificationFlow::Error::UnknownMethod);
+                                          return;
+                                  }
+                                  this->acceptVerificationRequest();
+                                  this->canonical_json = nlohmann::json(msg.content);
+                          } else {
+                                  this->cancelVerification(
+                                    DeviceVerificationFlow::Error::UnknownMethod);
+                          }
+                  }
+          });
         connect(
           ChatPage::instance(),
           &ChatPage::recievedDeviceVerificationAccept,
@@ -76,7 +87,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                                   this->mac_method = msg.content.message_authentication_code;
                                   this->sendVerificationKey();
                           } else {
-                                  this->cancelVerification();
+                                  this->cancelVerification(
+                                    DeviceVerificationFlow::Error::UnknownMethod);
                           }
                   }
           });
@@ -130,7 +142,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                                         msg.content.key + this->canonical_json.dump()))) {
                                           emit this->verificationRequestAccepted(this->method);
                                   } else {
-                                          this->cancelVerification();
+                                          this->cancelVerification(
+                                            DeviceVerificationFlow::Error::MismatchedCommitment);
                                   }
                           }
                   }
@@ -156,7 +169,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                                               this->sas->calculate_mac(this->device_keys[mac.first],
                                                                        info + mac.first)) {
                                           } else {
-                                                  this->cancelVerification();
+                                                  this->cancelVerification(
+                                                    DeviceVerificationFlow::Error::KeyMismatch);
                                                   return;
                                           }
                                   }
@@ -172,7 +186,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                                   else
                                           this->isMacVerified = true;
                           } else {
-                                  this->cancelVerification();
+                                  this->cancelVerification(
+                                    DeviceVerificationFlow::Error::KeyMismatch);
                           }
                   }
           });
@@ -429,15 +444,31 @@ DeviceVerificationFlow::sendVerificationRequest()
 }
 //! cancels a verification flow
 void
-DeviceVerificationFlow::cancelVerification()
+DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code)
 {
         mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationCancel req;
 
         req.transaction_id = this->transaction_id;
-        // TODO: Add Proper Error Messages and Code
-        req.reason = "Device Verification Cancelled";
-        req.code   = "400";
+        if (error_code == DeviceVerificationFlow::Error::UnknownMethod) {
+                req.code   = "m.unknown_method";
+                req.reason = "unknown method recieved";
+        } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) {
+                req.code   = "m.mismatched_commitment";
+                req.reason = "commitment didn't match";
+        } else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) {
+                req.code   = "m.mismatched_sas";
+                req.reason = "sas didn't match";
+        } else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) {
+                req.code   = "m.key_match";
+                req.reason = "keys did not match";
+        } else if (error_code == DeviceVerificationFlow::Error::Timeout) {
+                req.code   = "m.timeout";
+                req.reason = "timed out";
+        } else if (error_code == DeviceVerificationFlow::Error::User) {
+                req.code   = "m.user";
+                req.reason = "user cancelled the verification";
+        }
 
         body[this->toClient][deviceId.toStdString()] = req;
 
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index ea86a10..891c6ae 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -28,6 +28,16 @@ public:
                 Emoji
         };
         Q_ENUM(Method)
+        enum Error
+        {
+                UnknownMethod,
+                MismatchedCommitment,
+                MismatchedSAS,
+                KeyMismatch,
+                Timeout,
+                User
+        };
+        Q_ENUM(Error)
 
         DeviceVerificationFlow(QObject *parent = nullptr);
         QString getTransactionId();
@@ -56,7 +66,7 @@ public slots:
         //! starts the verification flow
         void startVerificationRequest();
         //! cancels a verification flow
-        void cancelVerification();
+        void cancelVerification(DeviceVerificationFlow::Error error_code);
         //! sends the verification key
         void sendVerificationKey();
         //! sends the mac of the keys
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 14c6695..0b73223 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -184,6 +184,9 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
                                     QString::fromStdString(msg.content.transaction_id),
                                     QString::fromStdString(msg.sender),
                                     QString::fromStdString(msg.content.from_device));
+                          } else {
+                                  flow->cancelVerification(
+                                    DeviceVerificationFlow::Error::UnknownMethod);
                           }
                   }
           });
@@ -197,11 +200,43 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
                   auto flow            = new DeviceVerificationFlow(this);
                   flow->canonical_json = nlohmann::json(msg.content);
                   if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) {
-                          emit newDeviceVerificationRequest(
-                            std::move(flow),
-                            QString::fromStdString(msg.content.transaction_id),
-                            QString::fromStdString(msg.sender),
-                            QString::fromStdString(msg.content.from_device));
+                          if ((std::find(msg.content.key_agreement_protocols.begin(),
+                                         msg.content.key_agreement_protocols.end(),
+                                         "curve25519-hkdf-sha256") !=
+                               msg.content.key_agreement_protocols.end()) &&
+                              (std::find(msg.content.hashes.begin(),
+                                         msg.content.hashes.end(),
+                                         "sha256") != msg.content.hashes.end()) &&
+                              (std::find(msg.content.message_authentication_codes.begin(),
+                                         msg.content.message_authentication_codes.end(),
+                                         "hmac-sha256") !=
+                               msg.content.message_authentication_codes.end())) {
+                                  if (std::find(msg.content.short_authentication_string.begin(),
+                                                msg.content.short_authentication_string.end(),
+                                                mtx::events::msg::SASMethods::Emoji) !=
+                                      msg.content.short_authentication_string.end()) {
+                                          flow->setMethod(DeviceVerificationFlow::Method::Emoji);
+                                  } else if (std::find(
+                                               msg.content.short_authentication_string.begin(),
+                                               msg.content.short_authentication_string.end(),
+                                               mtx::events::msg::SASMethods::Decimal) !=
+                                             msg.content.short_authentication_string.end()) {
+                                          flow->setMethod(DeviceVerificationFlow::Method::Decimal);
+                                  } else {
+                                          flow->cancelVerification(
+                                            DeviceVerificationFlow::Error::UnknownMethod);
+                                          return;
+                                  }
+                                  emit newDeviceVerificationRequest(
+                                    std::move(flow),
+                                    QString::fromStdString(msg.content.transaction_id),
+                                    QString::fromStdString(msg.sender),
+                                    QString::fromStdString(msg.content.from_device));
+                                  flow->canonical_json = nlohmann::json(msg.content);
+                          } else {
+                                  flow->cancelVerification(
+                                    DeviceVerificationFlow::Error::UnknownMethod);
+                          }
                   }
           });
 }

From 6fae36abc404ffb7e6ae29c9edceda5231400f0a Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sun, 28 Jun 2020 21:01:34 +0530
Subject: [PATCH 23/70] [WIP] Add Caching for users

---
 resources/qml/TimelineView.qml |  18 ++---
 resources/qml/UserProfile.qml  |  11 +--
 src/Cache.cpp                  | 138 +++++++++++++++++++++++++++++++++
 src/Cache.h                    |  17 ++++
 src/CacheCryptoStructs.h       |  30 +++++++
 src/Cache_p.h                  |  19 +++++
 src/ui/UserProfile.cpp         | 106 ++++++++++++++-----------
 src/ui/UserProfile.h           |   4 +
 8 files changed, 283 insertions(+), 60 deletions(-)

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index d9302ed..3618140 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -21,6 +21,7 @@ Page {
 	property real highlightHue: colors.highlight.hslHue
 	property real highlightSat: colors.highlight.hslSaturation
 	property real highlightLight: colors.highlight.hslLightness
+	property variant userProfile
 
 	palette: colors
 
@@ -238,6 +239,11 @@ Page {
 				}
 			}
 
+			Component{
+				id: userProfileComponent
+				UserProfile{}
+			}
+
 			section {
 				property: "section"
 			}
@@ -274,8 +280,6 @@ Page {
 						}
 					}
 
-					property variant userProfile
-
 					Row {
 						height: userName.height
 						spacing: 8
@@ -290,9 +294,7 @@ Page {
 							MouseArea {
 								anchors.fill: parent
                                 onClicked: {
-									if(userProfile) userProfile.destroy()
-									var component = Qt.createComponent("UserProfile.qml");
-									userProfile = component.createObject(timelineRoot,{user_data : modelData});
+									userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)});
 									userProfile.show();
                                 }
 								cursorShape: Qt.PointingHandCursor
@@ -310,10 +312,8 @@ Page {
 								anchors.fill: parent
 								Layout.alignment: Qt.AlignHCenter
                                 onClicked: {
-									if(userProfile) userProfile.destroy()
-									var component = Qt.createComponent("UserProfile.qml")
-									userProfile = component.createObject(timelineRoot,{user_data : modelData})
-									userProfile.show()
+									userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)});
+									userProfile.show();
                                 }
 								cursorShape: Qt.PointingHandCursor
 								propagateComposedEvents: true
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 6ef7503..a0b0f99 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -9,6 +9,7 @@ import "./device-verification"
 
 ApplicationWindow{
     property var user_data
+    property var avatarUrl
     property var colors: currentActivePalette
 
     id:userProfileDialog
@@ -52,11 +53,11 @@ ApplicationWindow{
 
             Avatar{
                 id: userProfileAvatar
-                url:chat.model.avatarUrl(user_data.userId).replace("mxc://", "image://MxcImage/")
+                url: avatarUrl.replace("mxc://", "image://MxcImage/")
                 height: 130
                 width: 130
-                displayName: modelData.userName
-		        userid: modelData.userId
+                displayName: user_data.userName
+		        userid: user_data.userId
                 Layout.alignment: Qt.AlignHCenter
                 Layout.margins : {
                     top: 10
@@ -68,7 +69,7 @@ ApplicationWindow{
                 text: user_data.userName
                 fontSizeMode: Text.HorizontalFit
                 font.pixelSize: 20
-                color:TimelineManager.userColor(modelData.userId, colors.window)
+                color:TimelineManager.userColor(user_data.userId, colors.window)
                 font.bold: true
                 Layout.alignment: Qt.AlignHCenter
             }
@@ -207,7 +208,7 @@ ApplicationWindow{
 
                 Layout.margins : {
                     right : 10
-                    bottom : 10
+                    bottom: 5
                 }
 
                 palette {
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 0c692d0..5afeab0 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2882,6 +2882,115 @@ Cache::statusMessage(const std::string &user_id)
         return status_msg;
 }
 
+void
+to_json(json &j, const UserCache &info)
+{
+        j["user_id"]          = info.user_id;
+        j["is_user_verified"] = info.is_user_verified;
+        j["cross_verified"]   = info.cross_verified;
+        j["keys"]             = info.keys;
+}
+
+void
+from_json(const json &j, UserCache &info)
+{
+        info.user_id          = j.at("user_id");
+        info.is_user_verified = j.at("is_user_verified");
+        info.cross_verified   = j.at("cross_verified").get>();
+        info.keys             = j.at("keys").get();
+}
+
+UserCache
+Cache::getUserCache(const std::string &user_id)
+{
+        lmdb::val verifiedVal;
+
+        auto txn = lmdb::txn::begin(env_);
+        auto db  = getUserCacheDb(txn);
+        auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
+
+        UserCache verified_state;
+        if (res) {
+                verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
+        }
+
+        txn.commit();
+
+        return verified_state;
+}
+
+//! be careful when using make sure is_user_verified is not changed
+int
+Cache::setUserCache(const std::string &user_id, const UserCache &body)
+{
+        auto txn = lmdb::txn::begin(env_);
+        auto db  = getUserCacheDb(txn);
+
+        auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump()));
+
+        txn.commit();
+
+        return res;
+}
+
+int
+Cache::deleteUserCache(const std::string &user_id)
+{
+        auto txn = lmdb::txn::begin(env_);
+        auto db  = getUserCacheDb(txn);
+        auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
+
+        txn.commit();
+
+        return res;
+}
+
+void
+to_json(json &j, const DeviceVerifiedCache &info)
+{
+        j["user_id"]         = info.user_id;
+        j["device_verified"] = info.device_verified;
+}
+
+void
+from_json(const json &j, DeviceVerifiedCache &info)
+{
+        info.user_id         = j.at("user_id");
+        info.device_verified = j.at("device_verified").get>();
+}
+
+DeviceVerifiedCache
+Cache::getVerifiedCache(const std::string &user_id)
+{
+        lmdb::val verifiedVal;
+
+        auto txn = lmdb::txn::begin(env_);
+        auto db  = getDeviceVerifiedDb(txn);
+        auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
+
+        DeviceVerifiedCache verified_state;
+        if (res) {
+                verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
+        }
+
+        txn.commit();
+
+        return verified_state;
+}
+
+int
+Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
+{
+        auto txn = lmdb::txn::begin(env_);
+        auto db  = getDeviceVerifiedDb(txn);
+
+        auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump()));
+
+        txn.commit();
+
+        return res;
+}
+
 void
 to_json(json &j, const RoomInfo &info)
 {
@@ -3063,6 +3172,35 @@ statusMessage(const std::string &user_id)
 {
         return instance_->statusMessage(user_id);
 }
+UserCache
+getUserCache(const std::string &user_id)
+{
+        return instance_->getUserCache(user_id);
+}
+
+int
+setUserCache(const std::string &user_id, const UserCache &body)
+{
+        return instance_->setUserCache(user_id, body);
+}
+
+int
+deleteUserCache(const std::string &user_id)
+{
+        return instance_->deleteUserCache(user_id);
+}
+
+DeviceVerifiedCache
+getVerifiedCache(const std::string &user_id)
+{
+        return instance_->getVerifiedCache(user_id);
+}
+
+int
+setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
+{
+        return instance_->setVerifiedCache(user_id, body);
+}
 
 //! Load saved data for the display names & avatars.
 void
diff --git a/src/Cache.h b/src/Cache.h
index b527562..34e79ab 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -60,6 +60,23 @@ presenceState(const std::string &user_id);
 std::string
 statusMessage(const std::string &user_id);
 
+//! user Cache
+UserCache
+getUserCache(const std::string &user_id);
+
+int
+setUserCache(const std::string &user_id, const UserCache &body);
+
+int
+deleteUserCache(const std::string &user_id);
+
+//! verified Cache
+DeviceVerifiedCache
+getVerifiedCache(const std::string &user_id);
+
+int
+setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body);
+
 //! Load saved data for the display names & avatars.
 void
 populateMembers();
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 14c9c86..7344aef 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -65,3 +65,33 @@ struct OlmSessionStorage
         std::mutex group_outbound_mtx;
         std::mutex group_inbound_mtx;
 };
+
+struct UserCache
+{
+        //! user_id of the user
+        std::string user_id;
+        //! this stores if the user is verified (with cross-signing)
+        bool is_user_verified = false;
+        //! list of verified device_ids with cross-signing
+        std::vector cross_verified;
+        //! map of public key key_ids and their public_key
+        mtx::responses::QueryKeys keys;
+};
+
+void
+to_json(nlohmann::json &j, const UserCache &info);
+void
+from_json(const nlohmann::json &j, UserCache &info);
+
+struct DeviceVerifiedCache
+{
+        //! user_id of the user
+        std::string user_id;
+        //! list of verified device_ids with device-verification
+        std::vector device_verified;
+};
+
+void
+to_json(nlohmann::json &j, const DeviceVerifiedCache &info);
+void
+from_json(const nlohmann::json &j, DeviceVerifiedCache &info);
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 61d91b0..cf4416c 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -54,6 +54,15 @@ public:
         mtx::presence::PresenceState presenceState(const std::string &user_id);
         std::string statusMessage(const std::string &user_id);
 
+        // user cache stores user keys
+        UserCache getUserCache(const std::string &user_id);
+        int setUserCache(const std::string &user_id, const UserCache &body);
+        int deleteUserCache(const std::string &user_id);
+
+        // device verified cache
+        DeviceVerifiedCache getVerifiedCache(const std::string &user_id);
+        int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body);
+
         static void removeDisplayName(const QString &room_id, const QString &user_id);
         static void removeAvatarUrl(const QString &room_id, const QString &user_id);
 
@@ -510,6 +519,16 @@ private:
                 return lmdb::dbi::open(txn, "presence", MDB_CREATE);
         }
 
+        lmdb::dbi getUserCacheDb(lmdb::txn &txn)
+        {
+                return lmdb::dbi::open(txn, std::string("user_cache").c_str(), MDB_CREATE);
+        }
+
+        lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn)
+        {
+                return lmdb::dbi::open(txn, std::string("verified").c_str(), MDB_CREATE);
+        }
+
         //! Retrieves or creates the database that stores the open OLM sessions between our device
         //! and the given curve25519 key which represents another device.
         //!
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 6aa4def..c637280 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -1,9 +1,12 @@
 #include "UserProfile.h"
+#include "Cache.h"
 #include "ChatPage.h"
 #include "Logging.h"
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
 
+#include  // only for debugging
+
 UserProfile::UserProfile(QObject *parent)
   : QObject(parent)
 {}
@@ -32,54 +35,65 @@ UserProfile::setUserId(const QString &user_id)
 }
 
 void
-UserProfile::fetchDeviceList(const QString &userID)
+UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
+                         mtx::http::RequestErr err,
+                         std::string user_id)
 {
-        auto localUser = utils::localUser();
-        mtx::requests::QueryKeys req;
-        mtx::responses::QueryKeys res;
-        req.device_keys[userID.toStdString()] = {};
-
-        http::client()->query_keys(
-          req,
-          [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
-                                                 mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to query device keys: {},{}",
-                                             err->matrix_error.errcode,
-                                             static_cast(err->status_code));
-                          return;
-                  }
-
-                  if (res.device_keys.empty() ||
-                      (res.device_keys.find(user_id) == res.device_keys.end())) {
-                          nhlog::net()->warn("no devices retrieved {}", user_id);
-                          return;
-                  }
-
-                  auto devices = res.device_keys.at(user_id);
-                  QVector deviceInfo;
-
-                  for (const auto &d : devices) {
-                          auto device = d.second;
-
-                          // TODO: Verify signatures and ignore those that don't pass.
-                          DeviceInfo newdevice(
-                            QString::fromStdString(d.first),
-                            QString::fromStdString(device.unsigned_info.device_display_name));
-                          QString::fromStdString(device.unsigned_info.device_display_name);
-
-                          deviceInfo.append(std::move(newdevice));
-                  }
-
-                  std::sort(deviceInfo.begin(),
-                            deviceInfo.end(),
-                            [](const DeviceInfo &a, const DeviceInfo &b) {
-                                    return a.device_id > b.device_id;
-                            });
-
-                  this->deviceList = std::move(deviceInfo);
-                  emit UserProfile::deviceListUpdated();
+        if (err) {
+                nhlog::net()->warn("failed to query device keys: {},{}",
+                                   err->matrix_error.errcode,
+                                   static_cast(err->status_code));
+                return;
+        }
+
+        if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) {
+                nhlog::net()->warn("no devices retrieved {}", user_id);
+                return;
+        }
+
+        auto devices = res.device_keys.at(user_id);
+        QVector deviceInfo;
+
+        for (const auto &d : devices) {
+                auto device = d.second;
+
+                // TODO: Verify signatures and ignore those that don't pass.
+                DeviceInfo newdevice(
+                  QString::fromStdString(d.first),
+                  QString::fromStdString(device.unsigned_info.device_display_name));
+                QString::fromStdString(device.unsigned_info.device_display_name);
+
+                deviceInfo.append(std::move(newdevice));
+        }
+
+        std::sort(
+          deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) {
+                  return a.device_id > b.device_id;
           });
+
+        this->deviceList = std::move(deviceInfo);
+        emit UserProfile::deviceListUpdated();
+}
+
+void
+UserProfile::fetchDeviceList(const QString &userID)
+{
+        auto localUser  = utils::localUser();
+        auto user_cache = cache::getUserCache(userID.toStdString());
+
+        if (user_cache.user_id == userID.toStdString()) {
+                mtx::http::ClientError error;
+                this->callback_fn(user_cache.keys, std::move(error), userID.toStdString());
+        } else {
+                mtx::requests::QueryKeys req;
+                req.device_keys[userID.toStdString()] = {};
+                http::client()->query_keys(
+                  req,
+                  [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
+                                                         mtx::http::RequestErr err) {
+                          this->callback_fn(res, err, user_id);
+                  });
+        }
 }
 
 void
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index ad92d18..befd82e 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -47,4 +47,8 @@ signals:
 private:
         QVector deviceList;
         QString userId;
+
+        void callback_fn(const mtx::responses::QueryKeys &res,
+                         mtx::http::RequestErr err,
+                         std::string user_id);
 };
\ No newline at end of file

From ac1fbbb69fdd4e313072cbf95eb9288db1257a9d Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Wed, 1 Jul 2020 17:47:10 +0530
Subject: [PATCH 24/70] Some issue with UserProfile

---
 CMakeLists.txt                |  3 ++
 resources/qml/UserProfile.qml | 44 +++++++++++-----------
 src/Cache.cpp                 | 32 ++++++++--------
 src/Cache.h                   |  4 +-
 src/CacheCryptoStructs.h      |  5 +--
 src/Cache_p.h                 |  4 +-
 src/ui/UserProfile.cpp        | 71 +++++++++++++++++++++++------------
 src/ui/UserProfile.h          | 54 +++++++++++++++++---------
 src/ui/UserProfileModel.cpp   | 45 +++++++++++-----------
 src/ui/UserProfileModel.h     |  7 ++--
 10 files changed, 154 insertions(+), 115 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 654eae3..c39ff3a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,9 @@ set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard")
 set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "Require C++ standard to be supported")
 set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default")
 
+# set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
+# set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
+
 option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
 include("cmake/HunterGate.cmake")
 HunterGate(
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index a0b0f99..db44ec1 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -19,17 +19,6 @@ ApplicationWindow{
     Layout.alignment: Qt.AlignHCenter
     palette: colors
 
-    UserProfileList{
-        id: userProfileList
-        userId: user_data.userId
-        onUserIdChanged : {
-            userProfileList.updateDeviceList()
-        }
-        onDeviceListUpdated : {
-            modelDeviceList.deviceList = userProfileList
-        }
-    }
-
     Component {
 		id: deviceVerificationDialog
 		DeviceVerification {}
@@ -94,7 +83,7 @@ ApplicationWindow{
                     ToolTip.visible: hovered
 			        ToolTip.text: qsTr("Ban the user")
                     onClicked : {
-                        userProfileList.banUser()
+                        modelDeviceList.deviceList.banUser()
                     }
                 }
                 // ImageButton{
@@ -106,7 +95,7 @@ ApplicationWindow{
                 //     ToolTip.visible: hovered
 			    //     ToolTip.text: qsTr("Ignore messages from this user")
                 //     onClicked : {
-                //         userProfileList.ignoreUser()
+                //         modelDeviceList.deviceList.ignoreUser()
                 //     }
                 // }
                 ImageButton{
@@ -118,7 +107,7 @@ ApplicationWindow{
                     ToolTip.visible: hovered
 			        ToolTip.text: qsTr("Start a private chat")
                     onClicked : {
-                        userProfileList.startChat()
+                        modelDeviceList.deviceList.startChat()
                     }
                 }
                 ImageButton{
@@ -130,7 +119,7 @@ ApplicationWindow{
                     ToolTip.visible: hovered
 			        ToolTip.text: qsTr("Kick the user")
                     onClicked : {
-                        userProfileList.kickUser()
+                        modelDeviceList.deviceList.kickUser()
                     }
                 }
             }
@@ -142,14 +131,15 @@ ApplicationWindow{
                 Layout.alignment: Qt.AlignHCenter
 
                 ListView{
-                    id: deviceList
+                    id: devicelist
                     anchors.fill: parent
                     clip: true
                     spacing: 4
 
                     model: UserProfileModel{
                         id: modelDeviceList
-                    } 
+                        deviceList.userId : user_data.userId
+                    }
 
                     delegate: RowLayout{
                         width: parent.width
@@ -157,12 +147,20 @@ ApplicationWindow{
                             top : 50
                         }
                         ColumnLayout{
-                            Text{
-                                Layout.fillWidth: true
-                                color: colors.text
-                                font.bold: true
-                                Layout.alignment: Qt.AlignRight
-                                text: deviceID
+                            RowLayout{
+                                Text{
+                                    Layout.fillWidth: true
+                                    color: colors.text
+                                    font.bold: true
+                                    Layout.alignment: Qt.AlignLeft
+                                    text: deviceID
+                                }
+                                Text{
+                                    Layout.fillWidth: true
+                                    color:colors.text
+                                    Layout.alignment: Qt.AlignLeft
+                                    text: (verified_status ==  UserProfileList.VERIFIED?"V":(verified_status ==  UserProfileList.UNVERIFIED?"NV":"B"))
+                                }
                             }
                             Text{
                                 Layout.fillWidth: true
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 5afeab0..553d1e9 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2885,7 +2885,6 @@ Cache::statusMessage(const std::string &user_id)
 void
 to_json(json &j, const UserCache &info)
 {
-        j["user_id"]          = info.user_id;
         j["is_user_verified"] = info.is_user_verified;
         j["cross_verified"]   = info.cross_verified;
         j["keys"]             = info.keys;
@@ -2894,13 +2893,12 @@ to_json(json &j, const UserCache &info)
 void
 from_json(const json &j, UserCache &info)
 {
-        info.user_id          = j.at("user_id");
         info.is_user_verified = j.at("is_user_verified");
         info.cross_verified   = j.at("cross_verified").get>();
         info.keys             = j.at("keys").get();
 }
 
-UserCache
+std::optional
 Cache::getUserCache(const std::string &user_id)
 {
         lmdb::val verifiedVal;
@@ -2909,14 +2907,15 @@ Cache::getUserCache(const std::string &user_id)
         auto db  = getUserCacheDb(txn);
         auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
 
+        txn.commit();
+
         UserCache verified_state;
         if (res) {
                 verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
+                return verified_state;
+        } else {
+                return {};
         }
-
-        txn.commit();
-
-        return verified_state;
 }
 
 //! be careful when using make sure is_user_verified is not changed
@@ -2948,18 +2947,18 @@ Cache::deleteUserCache(const std::string &user_id)
 void
 to_json(json &j, const DeviceVerifiedCache &info)
 {
-        j["user_id"]         = info.user_id;
         j["device_verified"] = info.device_verified;
+        j["device_blocked"]  = info.device_blocked;
 }
 
 void
 from_json(const json &j, DeviceVerifiedCache &info)
 {
-        info.user_id         = j.at("user_id");
         info.device_verified = j.at("device_verified").get>();
+        info.device_blocked  = j.at("device_blocked").get>();
 }
 
-DeviceVerifiedCache
+std::optional
 Cache::getVerifiedCache(const std::string &user_id)
 {
         lmdb::val verifiedVal;
@@ -2968,14 +2967,15 @@ Cache::getVerifiedCache(const std::string &user_id)
         auto db  = getDeviceVerifiedDb(txn);
         auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
 
+        txn.commit();
+
         DeviceVerifiedCache verified_state;
         if (res) {
                 verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
+                return verified_state;
+        } else {
+                return {};
         }
-
-        txn.commit();
-
-        return verified_state;
 }
 
 int
@@ -3172,7 +3172,7 @@ statusMessage(const std::string &user_id)
 {
         return instance_->statusMessage(user_id);
 }
-UserCache
+std::optional
 getUserCache(const std::string &user_id)
 {
         return instance_->getUserCache(user_id);
@@ -3190,7 +3190,7 @@ deleteUserCache(const std::string &user_id)
         return instance_->deleteUserCache(user_id);
 }
 
-DeviceVerifiedCache
+std::optional
 getVerifiedCache(const std::string &user_id)
 {
         return instance_->getVerifiedCache(user_id);
diff --git a/src/Cache.h b/src/Cache.h
index 34e79ab..0c955c9 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -61,7 +61,7 @@ std::string
 statusMessage(const std::string &user_id);
 
 //! user Cache
-UserCache
+std::optional
 getUserCache(const std::string &user_id);
 
 int
@@ -71,7 +71,7 @@ int
 deleteUserCache(const std::string &user_id);
 
 //! verified Cache
-DeviceVerifiedCache
+std::optional
 getVerifiedCache(const std::string &user_id);
 
 int
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 7344aef..241cac7 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -68,8 +68,6 @@ struct OlmSessionStorage
 
 struct UserCache
 {
-        //! user_id of the user
-        std::string user_id;
         //! this stores if the user is verified (with cross-signing)
         bool is_user_verified = false;
         //! list of verified device_ids with cross-signing
@@ -85,10 +83,9 @@ from_json(const nlohmann::json &j, UserCache &info);
 
 struct DeviceVerifiedCache
 {
-        //! user_id of the user
-        std::string user_id;
         //! list of verified device_ids with device-verification
         std::vector device_verified;
+        std::vector device_blocked;
 };
 
 void
diff --git a/src/Cache_p.h b/src/Cache_p.h
index cf4416c..60fa930 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -55,12 +55,12 @@ public:
         std::string statusMessage(const std::string &user_id);
 
         // user cache stores user keys
-        UserCache getUserCache(const std::string &user_id);
+        std::optional getUserCache(const std::string &user_id);
         int setUserCache(const std::string &user_id, const UserCache &body);
         int deleteUserCache(const std::string &user_id);
 
         // device verified cache
-        DeviceVerifiedCache getVerifiedCache(const std::string &user_id);
+        std::optional getVerifiedCache(const std::string &user_id);
         int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body);
 
         static void removeDisplayName(const QString &room_id, const QString &user_id);
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index c637280..8c6fb8e 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -7,11 +7,25 @@
 
 #include  // only for debugging
 
+Q_DECLARE_METATYPE(UserProfile::Status)
+
 UserProfile::UserProfile(QObject *parent)
   : QObject(parent)
-{}
+{
+        qRegisterMetaType();
+        connect(
+          this, &UserProfile::updateDeviceList, this, [this]() { fetchDeviceList(this->userId); });
+        connect(
+          this,
+          &UserProfile::appendDeviceList,
+          this,
+          [this](QString device_id, QString device_name, UserProfile::Status verification_status) {
+                  this->deviceList.push_back(
+                    DeviceInfo{device_id, device_name, verification_status});
+          });
+}
 
-QVector
+std::vector
 UserProfile::getDeviceList()
 {
         return this->deviceList;
@@ -37,7 +51,8 @@ UserProfile::setUserId(const QString &user_id)
 void
 UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
-                         std::string user_id)
+                         std::string user_id,
+                         std::optional> cross_verified)
 {
         if (err) {
                 nhlog::net()->warn("failed to query device keys: {},{}",
@@ -52,24 +67,40 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
         }
 
         auto devices = res.device_keys.at(user_id);
-        QVector deviceInfo;
+        std::vector deviceInfo;
+        auto device_verified = cache::getVerifiedCache(user_id);
 
         for (const auto &d : devices) {
                 auto device = d.second;
 
                 // TODO: Verify signatures and ignore those that don't pass.
-                DeviceInfo newdevice(
+                UserProfile::Status verified = UserProfile::Status::UNVERIFIED;
+                if (cross_verified.has_value()) {
+                        if (std::find(cross_verified->begin(), cross_verified->end(), d.first) !=
+                            cross_verified->end())
+                                verified = UserProfile::Status::VERIFIED;
+                } else if (device_verified.has_value()) {
+                        if (std::find(device_verified->device_verified.begin(),
+                                      device_verified->device_verified.end(),
+                                      d.first) != device_verified->device_verified.end())
+                                verified = UserProfile::Status::VERIFIED;
+                } else if (device_verified.has_value()) {
+                        if (std::find(device_verified->device_blocked.begin(),
+                                      device_verified->device_blocked.end(),
+                                      d.first) != device_verified->device_blocked.end())
+                                verified = UserProfile::Status::BLOCKED;
+                }
+
+                emit UserProfile::appendDeviceList(
                   QString::fromStdString(d.first),
-                  QString::fromStdString(device.unsigned_info.device_display_name));
-                QString::fromStdString(device.unsigned_info.device_display_name);
-
-                deviceInfo.append(std::move(newdevice));
+                  QString::fromStdString(device.unsigned_info.device_display_name),
+                  verified);
         }
 
-        std::sort(
-          deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) {
-                  return a.device_id > b.device_id;
-          });
+        // std::sort(
+        //   deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) {
+        //           return a.device_id > b.device_id;
+        //   });
 
         this->deviceList = std::move(deviceInfo);
         emit UserProfile::deviceListUpdated();
@@ -81,9 +112,9 @@ UserProfile::fetchDeviceList(const QString &userID)
         auto localUser  = utils::localUser();
         auto user_cache = cache::getUserCache(userID.toStdString());
 
-        if (user_cache.user_id == userID.toStdString()) {
-                mtx::http::ClientError error;
-                this->callback_fn(user_cache.keys, std::move(error), userID.toStdString());
+        if (user_cache.has_value()) {
+                this->callback_fn(
+                  user_cache->keys, {}, userID.toStdString(), user_cache->cross_verified);
         } else {
                 mtx::requests::QueryKeys req;
                 req.device_keys[userID.toStdString()] = {};
@@ -91,17 +122,11 @@ UserProfile::fetchDeviceList(const QString &userID)
                   req,
                   [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
                                                          mtx::http::RequestErr err) {
-                          this->callback_fn(res, err, user_id);
+                          this->callback_fn(res, err, user_id, {});
                   });
         }
 }
 
-void
-UserProfile::updateDeviceList()
-{
-        fetchDeviceList(this->userId);
-}
-
 void
 UserProfile::banUser()
 {
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index befd82e..1725b96 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -5,36 +5,32 @@
 #include 
 
 #include "MatrixClient.h"
-class DeviceInfo
-{
-public:
-        DeviceInfo(const QString deviceID, const QString displayName)
-          : device_id(deviceID)
-          , display_name(displayName)
-        {}
 
-        DeviceInfo() {}
-
-        QString device_id;
-        QString display_name;
-};
+class DeviceInfo;
 
 class UserProfile : public QObject
 {
         Q_OBJECT
         Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged)
-        Q_PROPERTY(QVector deviceList READ getDeviceList NOTIFY deviceListUpdated)
+        Q_PROPERTY(std::vector deviceList READ getDeviceList NOTIFY deviceListUpdated)
 public:
         // constructor
         explicit UserProfile(QObject *parent = 0);
         // getters
-        QVector getDeviceList();
+        std::vector getDeviceList();
         QString getUserId();
         // setters
         void setUserId(const QString &userId);
 
-        Q_INVOKABLE void fetchDeviceList(const QString &userID);
-        Q_INVOKABLE void updateDeviceList();
+        enum Status
+        {
+                VERIFIED,
+                UNVERIFIED,
+                BLOCKED
+        };
+        Q_ENUM(Status)
+
+        void fetchDeviceList(const QString &userID);
         Q_INVOKABLE void banUser();
         // Q_INVOKABLE void ignoreUser();
         Q_INVOKABLE void kickUser();
@@ -43,12 +39,34 @@ public:
 signals:
         void userIdChanged();
         void deviceListUpdated();
+        void updateDeviceList();
+        void appendDeviceList(const QString device_id,
+                              const QString device_naem,
+                              const UserProfile::Status verification_status);
 
 private:
-        QVector deviceList;
+        std::vector deviceList;
         QString userId;
+        std::optional cross_verified;
 
         void callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
-                         std::string user_id);
+                         std::string user_id,
+                         std::optional> cross_verified);
+};
+
+class DeviceInfo
+{
+public:
+        DeviceInfo(const QString deviceID,
+                   const QString displayName,
+                   UserProfile::Status verification_status_)
+          : device_id(deviceID)
+          , display_name(displayName)
+          , verification_status(verification_status_)
+        {}
+
+        QString device_id;
+        QString display_name;
+        UserProfile::Status verification_status;
 };
\ No newline at end of file
diff --git a/src/ui/UserProfileModel.cpp b/src/ui/UserProfileModel.cpp
index ec0456c..3fa8fe2 100644
--- a/src/ui/UserProfileModel.cpp
+++ b/src/ui/UserProfileModel.cpp
@@ -1,11 +1,23 @@
 #include "UserProfileModel.h"
-#include "UserProfile.h"
 #include 
 
 UserProfileModel::UserProfileModel(QObject *parent)
   : QAbstractListModel(parent)
   , deviceList(nullptr)
-{}
+{
+        this->deviceList = new UserProfile(this);
+
+        connect(this->deviceList, &UserProfile::userIdChanged, this, [this]() {
+                emit this->deviceList->updateDeviceList();
+        });
+        connect(this->deviceList, &UserProfile::deviceListUpdated, this, [this]() {
+                beginResetModel();
+                this->beginInsertRows(
+                  QModelIndex(), 0, this->deviceList->getDeviceList().size() - 1);
+                this->endInsertRows();
+                endResetModel();
+        });
+}
 
 int
 UserProfileModel::rowCount(const QModelIndex &parent) const
@@ -18,7 +30,8 @@ UserProfileModel::rowCount(const QModelIndex &parent) const
 QVariant
 UserProfileModel::data(const QModelIndex &index, int role) const
 {
-        if (!index.isValid() || !this->deviceList)
+        if (!index.isValid() &&
+            static_cast(this->deviceList->getDeviceList().size()) <= index.row())
                 return QVariant();
 
         const DeviceInfo device = this->deviceList->getDeviceList().at(index.row());
@@ -27,6 +40,8 @@ UserProfileModel::data(const QModelIndex &index, int role) const
                 return QVariant(device.device_id);
         case DISPLAYNAME:
                 return QVariant(device.display_name);
+        case VERIFIED_STATUS:
+                return device.verification_status;
         }
         return QVariant();
 }
@@ -35,8 +50,9 @@ QHash
 UserProfileModel::roleNames() const
 {
         QHash names;
-        names[DEVICEID]    = "deviceID";
-        names[DISPLAYNAME] = "displayName";
+        names[DEVICEID]        = "deviceID";
+        names[DISPLAYNAME]     = "displayName";
+        names[VERIFIED_STATUS] = "verified_status";
         return names;
 }
 
@@ -45,22 +61,3 @@ UserProfileModel::getList() const
 {
         return (this->deviceList);
 }
-
-void
-UserProfileModel::setList(UserProfile *devices)
-{
-        beginResetModel();
-
-        if (devices)
-                devices->disconnect(this);
-
-        if (this->deviceList) {
-                const int index = this->deviceList->getDeviceList().size();
-                beginInsertRows(QModelIndex(), index, index);
-                endInsertRows();
-        }
-
-        this->deviceList = devices;
-
-        endResetModel();
-}
\ No newline at end of file
diff --git a/src/ui/UserProfileModel.h b/src/ui/UserProfileModel.h
index c21a806..ba7a252 100644
--- a/src/ui/UserProfileModel.h
+++ b/src/ui/UserProfileModel.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "UserProfile.h"
 #include 
 
 class UserProfile; // forward declaration of the class UserProfile
@@ -7,7 +8,7 @@ class UserProfile; // forward declaration of the class UserProfile
 class UserProfileModel : public QAbstractListModel
 {
         Q_OBJECT
-        Q_PROPERTY(UserProfile *deviceList READ getList WRITE setList)
+        Q_PROPERTY(UserProfile *deviceList READ getList)
 
 public:
         explicit UserProfileModel(QObject *parent = nullptr);
@@ -15,10 +16,10 @@ public:
         enum
         {
                 DEVICEID,
-                DISPLAYNAME
+                DISPLAYNAME,
+                VERIFIED_STATUS
         };
         UserProfile *getList() const;
-        void setList(UserProfile *devices);
 
         int rowCount(const QModelIndex &parent = QModelIndex()) const override;
         QVariant data(const QModelIndex &index, int role) const override;

From 08028d5c57d134fb3d0ca9004730f0b2c99e5e67 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 4 Jul 2020 04:24:28 +0200
Subject: [PATCH 25/70] Refactor UserProfile

---
 CMakeLists.txt                       |   4 -
 resources/qml/TimelineView.qml       |  22 +-
 resources/qml/UserProfile.qml        | 417 +++++++++++++--------------
 src/MainWindow.cpp                   |   9 -
 src/MainWindow.h                     |   2 -
 src/dialogs/UserProfile.cpp          | 305 --------------------
 src/dialogs/UserProfile.h            |  69 -----
 src/timeline/TimelineModel.cpp       |   4 +-
 src/timeline/TimelineModel.h         |  10 +-
 src/timeline/TimelineViewManager.cpp |  20 +-
 src/ui/UserProfile.cpp               | 108 ++++---
 src/ui/UserProfile.h                 | 120 +++++---
 src/ui/UserProfileModel.cpp          |  63 ----
 src/ui/UserProfileModel.h            |  30 --
 14 files changed, 383 insertions(+), 800 deletions(-)
 delete mode 100644 src/dialogs/UserProfile.cpp
 delete mode 100644 src/dialogs/UserProfile.h
 delete mode 100644 src/ui/UserProfileModel.cpp
 delete mode 100644 src/ui/UserProfileModel.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c39ff3a..5ad8a62 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -240,7 +240,6 @@ set(SRC_FILES
 	src/dialogs/ReCaptcha.cpp
 	src/dialogs/ReadReceipts.cpp
 	src/dialogs/RoomSettings.cpp
-	src/dialogs/UserProfile.cpp
 
 	# Emoji
 	src/emoji/Category.cpp
@@ -280,7 +279,6 @@ set(SRC_FILES
 	src/ui/Theme.cpp
 	src/ui/ThemeManager.cpp
 	src/ui/UserProfile.cpp
-	src/ui/UserProfileModel.cpp
 
 	src/AvatarProvider.cpp
 	src/BlurhashProvider.cpp
@@ -449,7 +447,6 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/dialogs/ReCaptcha.h
 	src/dialogs/ReadReceipts.h
 	src/dialogs/RoomSettings.h
-	src/dialogs/UserProfile.h
 
 	# Emoji
 	src/emoji/Category.h
@@ -486,7 +483,6 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/ui/Theme.h
 	src/ui/ThemeManager.h
 	src/ui/UserProfile.h
-	src/ui/UserProfileModel.h
 
 	src/notifications/Manager.h
 
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 3618140..ec63487 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -106,17 +106,23 @@ Page {
 		}
 		Connections {
 			target: TimelineManager
-			onNewDeviceVerificationRequest: {
+			function onNewDeviceVerificationRequest(flow) {
 				flow.userId = userId;
 				flow.sender = false;
 				flow.deviceId = deviceId;
 				flow.tranId = transactionId;
 				deviceVerificationList.add(flow.tranId);
-				var dialog = deviceVerificationDialog.createObject(timelineRoot, 
-                    {flow: flow});
+				var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow});
 				dialog.show();
 			}
 		}
+		Connections {
+			target: TimelineManager.timeline
+			function onOpenProfile(profile) {
+				var userProfile = userProfileComponent.createObject(timelineRoot,{profile: profile});
+				userProfile.show();
+			}
+		}
 
 		Label {
 			visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
@@ -293,10 +299,7 @@ Page {
 
 							MouseArea {
 								anchors.fill: parent
-                                onClicked: {
-									userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)});
-									userProfile.show();
-                                }
+								onClicked: chat.model.openUserProfile(modelData.userId)
 								cursorShape: Qt.PointingHandCursor
 								propagateComposedEvents: true
 							}
@@ -311,10 +314,7 @@ Page {
 							MouseArea {
 								anchors.fill: parent
 								Layout.alignment: Qt.AlignHCenter
-                                onClicked: {
-									userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)});
-									userProfile.show();
-                                }
+								onClicked: chat.model.openUserProfile(modelData.userId)
 								cursorShape: Qt.PointingHandCursor
 								propagateComposedEvents: true
 							}
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index db44ec1..df54367 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -8,220 +8,211 @@ import im.nheko 1.0
 import "./device-verification"
 
 ApplicationWindow{
-    property var user_data
-    property var avatarUrl
-    property var colors: currentActivePalette
-
-    id:userProfileDialog
-    height: 650
-    width: 420
-    modality:Qt.WindowModal
-    Layout.alignment: Qt.AlignHCenter
-    palette: colors
-
-    Component {
+	property var profile
+
+	id: userProfileDialog
+	height: 650
+	width: 420
+	modality: Qt.WindowModal
+	Layout.alignment: Qt.AlignHCenter
+	palette: colors
+
+	Component {
 		id: deviceVerificationDialog
 		DeviceVerification {}
 	}
-    Component{
-        id: deviceVerificationFlow
-        DeviceVerificationFlow {}
-    }
-
-    background: Item{
-        id: userProfileItem
-        width: userProfileDialog.width
-        height: userProfileDialog.height
-
-        // Layout.fillHeight : true
-
-        ColumnLayout{
-            anchors.fill: userProfileItem
-            width: userProfileDialog.width
-            spacing: 10
-
-            Avatar{
-                id: userProfileAvatar
-                url: avatarUrl.replace("mxc://", "image://MxcImage/")
-                height: 130
-                width: 130
-                displayName: user_data.userName
-		        userid: user_data.userId
-                Layout.alignment: Qt.AlignHCenter
-                Layout.margins : {
-                    top: 10
-                }
-            }
-
-            Label{
-                id: userProfileName
-                text: user_data.userName
-                fontSizeMode: Text.HorizontalFit
-                font.pixelSize: 20
-                color:TimelineManager.userColor(user_data.userId, colors.window)
-                font.bold: true
-                Layout.alignment: Qt.AlignHCenter
-            }
-
-            Label{
-                id: matrixUserID
-                text: user_data.userId
-                fontSizeMode: Text.HorizontalFit
-                font.pixelSize: 15
-                color:colors.text
-                Layout.alignment: Qt.AlignHCenter
-            }
-
-            RowLayout{
-                Layout.alignment: Qt.AlignHCenter
-                ImageButton{
-                    image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
-                    Layout.margins: {
-                        left: 5
-                        right: 5
-                    }
-                    ToolTip.visible: hovered
-			        ToolTip.text: qsTr("Ban the user")
-                    onClicked : {
-                        modelDeviceList.deviceList.banUser()
-                    }
-                }
-                // ImageButton{
-                //     image:":/icons/icons/ui/volume-off-indicator.png"
-                //     Layout.margins: {
-                //         left: 5
-                //         right: 5
-                //     }
-                //     ToolTip.visible: hovered
-			    //     ToolTip.text: qsTr("Ignore messages from this user")
-                //     onClicked : {
-                //         modelDeviceList.deviceList.ignoreUser()
-                //     }
-                // }
-                ImageButton{
-                    image:":/icons/icons/ui/black-bubble-speech.png"
-                    Layout.margins: {
-                        left: 5
-                        right: 5
-                    }
-                    ToolTip.visible: hovered
-			        ToolTip.text: qsTr("Start a private chat")
-                    onClicked : {
-                        modelDeviceList.deviceList.startChat()
-                    }
-                }
-                ImageButton{
-                    image:":/icons/icons/ui/round-remove-button.png"
-                    Layout.margins: {
-                        left: 5
-                        right: 5
-                    }
-                    ToolTip.visible: hovered
-			        ToolTip.text: qsTr("Kick the user")
-                    onClicked : {
-                        modelDeviceList.deviceList.kickUser()
-                    }
-                }
-            }
-
-            ScrollView {
-                implicitHeight: userProfileDialog.height/2 + 20
-                implicitWidth: userProfileDialog.width-20
-                clip: true
-                Layout.alignment: Qt.AlignHCenter
-
-                ListView{
-                    id: devicelist
-                    anchors.fill: parent
-                    clip: true
-                    spacing: 4
-
-                    model: UserProfileModel{
-                        id: modelDeviceList
-                        deviceList.userId : user_data.userId
-                    }
-
-                    delegate: RowLayout{
-                        width: parent.width
-                        Layout.margins : {
-                            top : 50
-                        }
-                        ColumnLayout{
-                            RowLayout{
-                                Text{
-                                    Layout.fillWidth: true
-                                    color: colors.text
-                                    font.bold: true
-                                    Layout.alignment: Qt.AlignLeft
-                                    text: deviceID
-                                }
-                                Text{
-                                    Layout.fillWidth: true
-                                    color:colors.text
-                                    Layout.alignment: Qt.AlignLeft
-                                    text: (verified_status ==  UserProfileList.VERIFIED?"V":(verified_status ==  UserProfileList.UNVERIFIED?"NV":"B"))
-                                }
-                            }
-                            Text{
-                                Layout.fillWidth: true
-                                color:colors.text
-                                Layout.alignment: Qt.AlignRight
-                                text: displayName
-                            }
-                        }
-                        Button{
-                            id: verifyButton
-                            text:"Verify"
-                            onClicked: {
-                                var newFlow = deviceVerificationFlow.createObject(userProfileDialog,
-                                {userId : user_data.userId,sender: true,deviceId : model.deviceID});
-                                deviceVerificationList.add(newFlow.tranId);
-								var dialog = deviceVerificationDialog.createObject(userProfileDialog, 
-                                    {flow: newFlow});
-				                dialog.show();
-                            }
-                            Layout.margins:{
-                                right: 10
-                            }
-                            palette {
-                                button: "white"
-                            }
-                            contentItem: Text {
-                                text: verifyButton.text
-                                color: "black"
-                                horizontalAlignment: Text.AlignHCenter
-                                verticalAlignment: Text.AlignVCenter
-                            }
-                        }
-                    }
-                }
-            }
-
-            Button{
-                id: okbutton
-                text:"OK"
-                onClicked: userProfileDialog.close()
-                
-                Layout.alignment: Qt.AlignRight | Qt.AlignBottom
-
-                Layout.margins : {
-                    right : 10
-                    bottom: 5
-                }
-
-                palette {
-                    button: "white"
-                }
-
-                contentItem: Text {
-                    text: okbutton.text
-                    color: "black"
-                    horizontalAlignment: Text.AlignHCenter
-                    verticalAlignment: Text.AlignVCenter
-                }
-            }
-        }
-
-        Item { Layout.fillHeight: true }
-    }
+	Component{
+		id: deviceVerificationFlow
+		DeviceVerificationFlow {}
+	}
+
+	background: Item{
+		id: userProfileItem
+		width: userProfileDialog.width
+		height: userProfileDialog.height
+
+		// Layout.fillHeight : true
+
+		ColumnLayout{
+			anchors.fill: userProfileItem
+			width: userProfileDialog.width
+			spacing: 10
+
+			Avatar {
+				url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
+				height: 130
+				width: 130
+				displayName: profile.displayName
+				userid: profile.userid
+				Layout.alignment: Qt.AlignHCenter
+				Layout.margins : {
+					top: 10
+				}
+			}
+
+			Label {
+				text: profile.displayName
+				fontSizeMode: Text.HorizontalFit
+				font.pixelSize: 20
+				color: TimelineManager.userColor(profile.userid, colors.window)
+				font.bold: true
+				Layout.alignment: Qt.AlignHCenter
+			}
+
+			Label {
+				text: profile.userid
+				fontSizeMode: Text.HorizontalFit
+				font.pixelSize: 15
+				color: colors.text
+				Layout.alignment: Qt.AlignHCenter
+			}
+
+			RowLayout {
+				Layout.alignment: Qt.AlignHCenter
+				ImageButton {
+					image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
+					Layout.margins: {
+						left: 5
+						right: 5
+					}
+					ToolTip.visible: hovered
+					ToolTip.text: qsTr("Ban the user")
+					onClicked : {
+						profile.banUser()
+					}
+				}
+				// ImageButton{
+				//     image:":/icons/icons/ui/volume-off-indicator.png"
+				//     Layout.margins: {
+				//         left: 5
+				//         right: 5
+				//     }
+				//     ToolTip.visible: hovered
+				//     ToolTip.text: qsTr("Ignore messages from this user")
+				//     onClicked : {
+				//         profile.ignoreUser()
+				//     }
+				// }
+				ImageButton{
+					image:":/icons/icons/ui/black-bubble-speech.png"
+					Layout.margins: {
+						left: 5
+						right: 5
+					}
+					ToolTip.visible: hovered
+					ToolTip.text: qsTr("Start a private chat")
+					onClicked : {
+						profile.startChat()
+					}
+				}
+				ImageButton{
+					image:":/icons/icons/ui/round-remove-button.png"
+					Layout.margins: {
+						left: 5
+						right: 5
+					}
+					ToolTip.visible: hovered
+					ToolTip.text: qsTr("Kick the user")
+					onClicked : {
+						profile.kickUser()
+					}
+				}
+			}
+
+			ScrollView {
+				implicitHeight: userProfileDialog.height/2 + 20
+				implicitWidth: userProfileDialog.width-20
+				clip: true
+				Layout.alignment: Qt.AlignHCenter
+
+				ListView{
+					id: devicelist
+					anchors.fill: parent
+					clip: true
+					spacing: 4
+
+					model: profile.deviceList
+
+					delegate: RowLayout{
+						width: parent.width
+						Layout.margins : {
+							top : 50
+						}
+						ColumnLayout{
+							RowLayout{
+								Text{
+									Layout.fillWidth: true
+									color: colors.text
+									font.bold: true
+									Layout.alignment: Qt.AlignLeft
+									text: model.deviceId
+								}
+								Text{
+									Layout.fillWidth: true
+									color:colors.text
+									Layout.alignment: Qt.AlignLeft
+									text: (model.verificationStatus ==  VerificationStatus.VERIFIED?"V":(model.verificationStatus ==  VerificationStatus.UNVERIFIED?"NV":"B"))
+								}
+							}
+							Text{
+								Layout.fillWidth: true
+								color:colors.text
+								Layout.alignment: Qt.AlignRight
+								text: model.deviceName
+							}
+						}
+						Button{
+							id: verifyButton
+							text:"Verify"
+							onClicked: {
+								var newFlow = deviceVerificationFlow.createObject(userProfileDialog,
+								{userId : profile.userid, sender: true, deviceId : model.deviceID});
+								deviceVerificationList.add(newFlow.tranId);
+								var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow});
+								dialog.show();
+							}
+							Layout.margins:{
+								right: 10
+							}
+							palette {
+								button: "white"
+							}
+							contentItem: Text {
+								text: verifyButton.text
+								color: "black"
+								horizontalAlignment: Text.AlignHCenter
+								verticalAlignment: Text.AlignVCenter
+							}
+						}
+					}
+				}
+			}
+
+			Button{
+				id: okbutton
+				text:"OK"
+				onClicked: userProfileDialog.close()
+
+				Layout.alignment: Qt.AlignRight | Qt.AlignBottom
+
+				Layout.margins : {
+					right : 10
+					bottom: 5
+				}
+
+				palette {
+					button: "white"
+				}
+
+				contentItem: Text {
+					text: okbutton.text
+					color: "black"
+					horizontalAlignment: Text.AlignHCenter
+					verticalAlignment: Text.AlignVCenter
+				}
+			}
+		}
+
+		Item { Layout.fillHeight: true }
+	}
 }
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index cc1d868..63b524c 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -317,15 +317,6 @@ MainWindow::hasActiveUser()
                settings.contains("auth/user_id");
 }
 
-void
-MainWindow::openUserProfile(const QString &user_id, const QString &room_id)
-{
-        auto dialog = new dialogs::UserProfile(this);
-        dialog->init(user_id, room_id);
-
-        showDialog(dialog);
-}
-
 void
 MainWindow::openRoomSettings(const QString &room_id)
 {
diff --git a/src/MainWindow.h b/src/MainWindow.h
index e3e0469..2fc2d00 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -25,7 +25,6 @@
 #include 
 
 #include "UserSettingsPage.h"
-#include "dialogs/UserProfile.h"
 #include "ui/OverlayModal.h"
 
 #include "jdenticoninterface.h"
@@ -76,7 +75,6 @@ public:
         void openLogoutDialog();
         void openRoomSettings(const QString &room_id = "");
         void openMemberListDialog(const QString &room_id = "");
-        void openUserProfile(const QString &user_id, const QString &room_id);
         void openReadReceiptsDialog(const QString &event_id);
 
         void hideOverlay();
diff --git a/src/dialogs/UserProfile.cpp b/src/dialogs/UserProfile.cpp
deleted file mode 100644
index 3415b12..0000000
--- a/src/dialogs/UserProfile.cpp
+++ /dev/null
@@ -1,305 +0,0 @@
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#include "Cache.h"
-#include "ChatPage.h"
-#include "Logging.h"
-#include "MatrixClient.h"
-#include "Utils.h"
-#include "dialogs/UserProfile.h"
-#include "ui/Avatar.h"
-#include "ui/FlatButton.h"
-
-using namespace dialogs;
-
-Q_DECLARE_METATYPE(std::vector)
-
-constexpr int BUTTON_SIZE       = 36;
-constexpr int BUTTON_RADIUS     = BUTTON_SIZE / 2;
-constexpr int WIDGET_MARGIN     = 20;
-constexpr int TOP_WIDGET_MARGIN = 2 * WIDGET_MARGIN;
-constexpr int WIDGET_SPACING    = 15;
-constexpr int TEXT_SPACING      = 4;
-constexpr int DEVICE_SPACING    = 5;
-
-DeviceItem::DeviceItem(DeviceInfo device, QWidget *parent)
-  : QWidget(parent)
-  , info_(std::move(device))
-{
-        QFont font;
-        font.setBold(true);
-
-        auto deviceIdLabel = new QLabel(info_.device_id, this);
-        deviceIdLabel->setFont(font);
-
-        auto layout = new QVBoxLayout{this};
-        layout->addWidget(deviceIdLabel);
-
-        if (!info_.display_name.isEmpty())
-                layout->addWidget(new QLabel(info_.display_name, this));
-
-        layout->setMargin(0);
-        layout->setSpacing(4);
-}
-
-UserProfile::UserProfile(QWidget *parent)
-  : QWidget(parent)
-{
-        setAutoFillBackground(true);
-        setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
-        setAttribute(Qt::WA_DeleteOnClose, true);
-
-        QIcon banIcon, kickIcon, ignoreIcon, startChatIcon;
-
-        banIcon.addFile(":/icons/icons/ui/do-not-disturb-rounded-sign.png");
-        banBtn_ = new FlatButton(this);
-        banBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
-        banBtn_->setCornerRadius(BUTTON_RADIUS);
-        banBtn_->setIcon(banIcon);
-        banBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
-        banBtn_->setToolTip(tr("Ban the user from the room"));
-
-        ignoreIcon.addFile(":/icons/icons/ui/volume-off-indicator.png");
-        ignoreBtn_ = new FlatButton(this);
-        ignoreBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
-        ignoreBtn_->setCornerRadius(BUTTON_RADIUS);
-        ignoreBtn_->setIcon(ignoreIcon);
-        ignoreBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
-        ignoreBtn_->setToolTip(tr("Ignore messages from this user"));
-        ignoreBtn_->setDisabled(true); // Not used yet.
-
-        kickIcon.addFile(":/icons/icons/ui/round-remove-button.png");
-        kickBtn_ = new FlatButton(this);
-        kickBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
-        kickBtn_->setCornerRadius(BUTTON_RADIUS);
-        kickBtn_->setIcon(kickIcon);
-        kickBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
-        kickBtn_->setToolTip(tr("Kick the user from the room"));
-
-        startChatIcon.addFile(":/icons/icons/ui/black-bubble-speech.png");
-        startChat_ = new FlatButton(this);
-        startChat_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
-        startChat_->setCornerRadius(BUTTON_RADIUS);
-        startChat_->setIcon(startChatIcon);
-        startChat_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
-        startChat_->setToolTip(tr("Start a conversation"));
-
-        connect(startChat_, &QPushButton::clicked, this, [this]() {
-                auto user_id = userIdLabel_->text();
-
-                mtx::requests::CreateRoom req;
-                req.preset     = mtx::requests::Preset::PrivateChat;
-                req.visibility = mtx::requests::Visibility::Private;
-
-                if (utils::localUser() != user_id)
-                        req.invite = {user_id.toStdString()};
-
-                emit ChatPage::instance()->createRoom(req);
-        });
-
-        connect(banBtn_, &QPushButton::clicked, this, [this] {
-                ChatPage::instance()->banUser(userIdLabel_->text(), "");
-        });
-        connect(kickBtn_, &QPushButton::clicked, this, [this] {
-                ChatPage::instance()->kickUser(userIdLabel_->text(), "");
-        });
-
-        // Button line
-        auto btnLayout = new QHBoxLayout;
-        btnLayout->addStretch(1);
-        btnLayout->addWidget(startChat_);
-        btnLayout->addWidget(ignoreBtn_);
-
-        btnLayout->addWidget(kickBtn_);
-        btnLayout->addWidget(banBtn_);
-        btnLayout->addStretch(1);
-        btnLayout->setSpacing(8);
-        btnLayout->setMargin(0);
-
-        avatar_ = new Avatar(this, 128);
-        avatar_->setLetter("X");
-
-        QFont font;
-        font.setPointSizeF(font.pointSizeF() * 2);
-
-        userIdLabel_      = new QLabel(this);
-        displayNameLabel_ = new QLabel(this);
-        displayNameLabel_->setFont(font);
-
-        auto textLayout = new QVBoxLayout;
-        textLayout->addWidget(displayNameLabel_);
-        textLayout->addWidget(userIdLabel_);
-        textLayout->setAlignment(displayNameLabel_, Qt::AlignCenter | Qt::AlignTop);
-        textLayout->setAlignment(userIdLabel_, Qt::AlignCenter | Qt::AlignTop);
-        textLayout->setSpacing(TEXT_SPACING);
-        textLayout->setMargin(0);
-
-        devices_ = new QListWidget{this};
-        devices_->setFrameStyle(QFrame::NoFrame);
-        devices_->setSelectionMode(QAbstractItemView::NoSelection);
-        devices_->setAttribute(Qt::WA_MacShowFocusRect, 0);
-        devices_->setSpacing(DEVICE_SPACING);
-
-        QFont descriptionLabelFont;
-        descriptionLabelFont.setWeight(65);
-
-        devicesLabel_ = new QLabel(tr("Devices").toUpper(), this);
-        devicesLabel_->setFont(descriptionLabelFont);
-        devicesLabel_->hide();
-        devicesLabel_->setFixedSize(devicesLabel_->sizeHint());
-
-        auto okBtn = new QPushButton("OK", this);
-
-        auto closeLayout = new QHBoxLayout();
-        closeLayout->setSpacing(15);
-        closeLayout->addStretch(1);
-        closeLayout->addWidget(okBtn);
-
-        auto vlayout = new QVBoxLayout{this};
-        vlayout->addWidget(avatar_, 0, Qt::AlignCenter | Qt::AlignTop);
-        vlayout->addLayout(textLayout);
-        vlayout->addLayout(btnLayout);
-        vlayout->addWidget(devicesLabel_, 0, Qt::AlignLeft);
-        vlayout->addWidget(devices_, 1);
-        vlayout->addLayout(closeLayout);
-
-        QFont largeFont;
-        largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
-
-        setMinimumWidth(
-          std::max(devices_->sizeHint().width() + 4 * WIDGET_MARGIN, conf::window::minModalWidth));
-        setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
-
-        vlayout->setSpacing(WIDGET_SPACING);
-        vlayout->setContentsMargins(WIDGET_MARGIN, TOP_WIDGET_MARGIN, WIDGET_MARGIN, WIDGET_MARGIN);
-
-        qRegisterMetaType>();
-
-        auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this);
-        connect(closeShortcut, &QShortcut::activated, this, &UserProfile::close);
-        connect(okBtn, &QPushButton::clicked, this, &UserProfile::close);
-}
-
-void
-UserProfile::resetToDefaults()
-{
-        avatar_->setLetter("X");
-        devices_->clear();
-
-        ignoreBtn_->show();
-        devices_->hide();
-        devicesLabel_->hide();
-}
-
-void
-UserProfile::init(const QString &userId, const QString &roomId)
-{
-        resetToDefaults();
-
-        auto displayName = cache::displayName(roomId, userId);
-
-        userIdLabel_->setText(userId);
-        displayNameLabel_->setText(displayName);
-        avatar_->setLetter(utils::firstChar(displayName));
-
-        avatar_->setImage(roomId, userId);
-
-        auto localUser = utils::localUser();
-
-        try {
-                bool hasMemberRights =
-                  cache::hasEnoughPowerLevel({mtx::events::EventType::RoomMember},
-                                             roomId.toStdString(),
-                                             localUser.toStdString());
-                if (!hasMemberRights) {
-                        kickBtn_->hide();
-                        banBtn_->hide();
-                } else {
-                        kickBtn_->show();
-                        banBtn_->show();
-                }
-        } catch (const lmdb::error &e) {
-                nhlog::db()->warn("lmdb error: {}", e.what());
-        }
-
-        if (localUser == userId) {
-                // TODO: click on display name & avatar to change.
-                kickBtn_->hide();
-                banBtn_->hide();
-                ignoreBtn_->hide();
-        }
-
-        mtx::requests::QueryKeys req;
-        req.device_keys[userId.toStdString()] = {};
-
-        // A proxy object is used to emit the signal instead of the original object
-        // which might be destroyed by the time the http call finishes.
-        auto proxy = std::make_shared();
-        QObject::connect(proxy.get(), &Proxy::done, this, &UserProfile::updateDeviceList);
-
-        http::client()->query_keys(
-          req,
-          [user_id = userId.toStdString(), proxy = std::move(proxy)](
-            const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to query device keys: {} {}",
-                                             err->matrix_error.error,
-                                             static_cast(err->status_code));
-                          // TODO: Notify the UI.
-                          return;
-                  }
-
-                  if (res.device_keys.empty() ||
-                      (res.device_keys.find(user_id) == res.device_keys.end())) {
-                          nhlog::net()->warn("no devices retrieved {}", user_id);
-                          return;
-                  }
-
-                  auto devices = res.device_keys.at(user_id);
-
-                  std::vector deviceInfo;
-                  for (const auto &d : devices) {
-                          auto device = d.second;
-
-                          // TODO: Verify signatures and ignore those that don't pass.
-                          deviceInfo.emplace_back(DeviceInfo{
-                            QString::fromStdString(d.first),
-                            QString::fromStdString(device.unsigned_info.device_display_name)});
-                  }
-
-                  std::sort(deviceInfo.begin(),
-                            deviceInfo.end(),
-                            [](const DeviceInfo &a, const DeviceInfo &b) {
-                                    return a.device_id > b.device_id;
-                            });
-
-                  if (!deviceInfo.empty())
-                          emit proxy->done(QString::fromStdString(user_id), deviceInfo);
-          });
-}
-
-void
-UserProfile::updateDeviceList(const QString &user_id, const std::vector &devices)
-{
-        if (user_id != userIdLabel_->text())
-                return;
-
-        for (const auto &dev : devices) {
-                auto deviceItem = new DeviceItem(dev, this);
-                auto item       = new QListWidgetItem;
-
-                item->setSizeHint(deviceItem->minimumSizeHint());
-                item->setFlags(Qt::NoItemFlags);
-                item->setTextAlignment(Qt::AlignCenter);
-
-                devices_->insertItem(devices_->count() - 1, item);
-                devices_->setItemWidget(item, deviceItem);
-        }
-
-        devicesLabel_->show();
-        devices_->show();
-        adjustSize();
-}
diff --git a/src/dialogs/UserProfile.h b/src/dialogs/UserProfile.h
deleted file mode 100644
index 81276d2..0000000
--- a/src/dialogs/UserProfile.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-#include 
-#include 
-
-class Avatar;
-class FlatButton;
-class QLabel;
-class QListWidget;
-class Toggle;
-
-struct DeviceInfo
-{
-        QString device_id;
-        QString display_name;
-};
-
-class Proxy : public QObject
-{
-        Q_OBJECT
-
-signals:
-        void done(const QString &user_id, const std::vector &devices);
-};
-
-namespace dialogs {
-
-class DeviceItem : public QWidget
-{
-        Q_OBJECT
-
-public:
-        explicit DeviceItem(DeviceInfo device, QWidget *parent);
-
-private:
-        DeviceInfo info_;
-
-        // Toggle *verifyToggle_;
-};
-
-class UserProfile : public QWidget
-{
-        Q_OBJECT
-public:
-        explicit UserProfile(QWidget *parent = nullptr);
-
-        void init(const QString &userId, const QString &roomId);
-
-private slots:
-        void updateDeviceList(const QString &user_id, const std::vector &devices);
-
-private:
-        void resetToDefaults();
-
-        Avatar *avatar_;
-
-        QLabel *userIdLabel_;
-        QLabel *displayNameLabel_;
-
-        FlatButton *banBtn_;
-        FlatButton *kickBtn_;
-        FlatButton *ignoreBtn_;
-        FlatButton *startChat_;
-
-        QLabel *devicesLabel_;
-        QListWidget *devices_;
-};
-
-} // dialogs
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index f41e771..773a5a2 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -654,9 +654,9 @@ TimelineModel::viewDecryptedRawMessage(QString id) const
 }
 
 void
-TimelineModel::openUserProfile(QString userid) const
+TimelineModel::openUserProfile(QString userid)
 {
-        MainWindow::instance()->openUserProfile(userid, room_id_);
+        emit openProfile(new UserProfile(room_id_, userid, this));
 }
 
 void
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index f8a84f1..104a475 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -9,7 +9,12 @@
 #include 
 
 #include "CacheCryptoStructs.h"
+<<<<<<< HEAD
 #include "EventStore.h"
+=======
+#include "ReactionsModel.h"
+#include "ui/UserProfile.h"
+>>>>>>> Refactor UserProfile
 
 namespace mtx::http {
 using RequestErr = const std::optional &;
@@ -188,7 +193,7 @@ public:
         Q_INVOKABLE QString escapeEmoji(QString str) const;
         Q_INVOKABLE void viewRawMessage(QString id) const;
         Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
-        Q_INVOKABLE void openUserProfile(QString userid) const;
+        Q_INVOKABLE void openUserProfile(QString userid);
         Q_INVOKABLE void replyAction(QString id);
         Q_INVOKABLE void readReceiptsAction(QString id) const;
         Q_INVOKABLE void redactEvent(QString id);
@@ -256,8 +261,7 @@ signals:
         void replyChanged(QString reply);
         void paginationInProgressChanged(const bool);
 
-        void newMessageToSend(mtx::events::collections::TimelineEvents event);
-        void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
+        void openProfile(UserProfile *profile);
 
 private:
         void sendEncryptedMessage(const std::string txn_id, nlohmann::json content);
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 0b73223..81c8d6d 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -16,10 +16,9 @@
 #include "dialogs/ImageOverlay.h"
 #include "emoji/EmojiModel.h"
 #include "emoji/Provider.h"
-#include "src/ui/UserProfile.h"
-#include "src/ui/UserProfileModel.h"
 
 Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
+Q_DECLARE_METATYPE(std::vector)
 
 namespace msgs = mtx::events::msg;
 
@@ -109,15 +108,28 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
                                          0,
                                          "MtxEvent",
                                          "Can't instantiate enum!");
+        qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
+                                         "im.nheko",
+                                         1,
+                                         0,
+                                         "VerificationStatus",
+                                         "Can't instantiate enum!");
+
         qmlRegisterType("im.nheko", 1, 0, "DelegateChoice");
         qmlRegisterType("im.nheko", 1, 0, "DelegateChooser");
         qmlRegisterType("im.nheko", 1, 0, "DeviceVerificationFlow");
-        qmlRegisterType("im.nheko", 1, 0, "UserProfileModel");
-        qmlRegisterType("im.nheko", 1, 0, "UserProfileList");
+        qmlRegisterUncreatableType(
+          "im.nheko",
+          1,
+          0,
+          "UserProfileModel",
+          "UserProfile needs to be instantiated on the C++ side");
         qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", this);
         qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", settings.data());
 
         qRegisterMetaType();
+        qRegisterMetaType>();
+
         qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel");
         qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel");
         qmlRegisterUncreatableType(
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 8c6fb8e..fde0044 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -5,47 +5,72 @@
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
 
-#include  // only for debugging
+UserProfile::UserProfile(QString roomid, QString userid, QObject *parent)
+  : QObject(parent)
+  , roomid_(roomid)
+  , userid_(userid)
+{
+        fetchDeviceList(this->userid_);
+}
 
-Q_DECLARE_METATYPE(UserProfile::Status)
+QHash
+DeviceInfoModel::roleNames() const
+{
+        return {
+          {DeviceId, "deviceId"},
+          {DeviceName, "deviceName"},
+          {VerificationStatus, "verificationStatus"},
+        };
+}
 
-UserProfile::UserProfile(QObject *parent)
-  : QObject(parent)
+QVariant
+DeviceInfoModel::data(const QModelIndex &index, int role) const
 {
-        qRegisterMetaType();
-        connect(
-          this, &UserProfile::updateDeviceList, this, [this]() { fetchDeviceList(this->userId); });
-        connect(
-          this,
-          &UserProfile::appendDeviceList,
-          this,
-          [this](QString device_id, QString device_name, UserProfile::Status verification_status) {
-                  this->deviceList.push_back(
-                    DeviceInfo{device_id, device_name, verification_status});
-          });
+        if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0)
+                return {};
+
+        switch (role) {
+        case DeviceId:
+                return deviceList_[index.row()].device_id;
+        case DeviceName:
+                return deviceList_[index.row()].display_name;
+        case VerificationStatus:
+                return QVariant::fromValue(deviceList_[index.row()].verification_status);
+        default:
+                return {};
+        }
 }
 
-std::vector
-UserProfile::getDeviceList()
+void
+DeviceInfoModel::reset(const std::vector &deviceList)
 {
-        return this->deviceList;
+        beginResetModel();
+        this->deviceList_ = std::move(deviceList);
+        endResetModel();
+}
+
+DeviceInfoModel *
+UserProfile::deviceList()
+{
+        return &this->deviceList_;
 }
 
 QString
-UserProfile::getUserId()
+UserProfile::userid()
 {
-        return this->userId;
+        return this->userid_;
 }
 
-void
-UserProfile::setUserId(const QString &user_id)
+QString
+UserProfile::displayName()
 {
-        if (this->userId != userId)
-                return;
-        else {
-                this->userId = user_id;
-                emit UserProfile::userIdChanged();
-        }
+        return cache::displayName(roomid_, userid_);
+}
+
+QString
+UserProfile::avatarUrl()
+{
+        return cache::avatarUrl(roomid_, userid_);
 }
 
 void
@@ -74,27 +99,27 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
                 auto device = d.second;
 
                 // TODO: Verify signatures and ignore those that don't pass.
-                UserProfile::Status verified = UserProfile::Status::UNVERIFIED;
+                verification::Status verified = verification::Status::UNVERIFIED;
                 if (cross_verified.has_value()) {
                         if (std::find(cross_verified->begin(), cross_verified->end(), d.first) !=
                             cross_verified->end())
-                                verified = UserProfile::Status::VERIFIED;
+                                verified = verification::Status::VERIFIED;
                 } else if (device_verified.has_value()) {
                         if (std::find(device_verified->device_verified.begin(),
                                       device_verified->device_verified.end(),
                                       d.first) != device_verified->device_verified.end())
-                                verified = UserProfile::Status::VERIFIED;
+                                verified = verification::Status::VERIFIED;
                 } else if (device_verified.has_value()) {
                         if (std::find(device_verified->device_blocked.begin(),
                                       device_verified->device_blocked.end(),
                                       d.first) != device_verified->device_blocked.end())
-                                verified = UserProfile::Status::BLOCKED;
+                                verified = verification::Status::BLOCKED;
                 }
 
-                emit UserProfile::appendDeviceList(
-                  QString::fromStdString(d.first),
-                  QString::fromStdString(device.unsigned_info.device_display_name),
-                  verified);
+                deviceInfo.push_back(
+                  {QString::fromStdString(d.first),
+                   QString::fromStdString(device.unsigned_info.device_display_name),
+                   verified});
         }
 
         // std::sort(
@@ -102,8 +127,7 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
         //           return a.device_id > b.device_id;
         //   });
 
-        this->deviceList = std::move(deviceInfo);
-        emit UserProfile::deviceListUpdated();
+        this->deviceList_.queueReset(std::move(deviceInfo));
 }
 
 void
@@ -130,7 +154,7 @@ UserProfile::fetchDeviceList(const QString &userID)
 void
 UserProfile::banUser()
 {
-        ChatPage::instance()->banUser(this->userId, "");
+        ChatPage::instance()->banUser(this->userid_, "");
 }
 
 // void ignoreUser(){
@@ -140,7 +164,7 @@ UserProfile::banUser()
 void
 UserProfile::kickUser()
 {
-        ChatPage::instance()->kickUser(this->userId, "");
+        ChatPage::instance()->kickUser(this->userid_, "");
 }
 
 void
@@ -149,7 +173,7 @@ UserProfile::startChat()
         mtx::requests::CreateRoom req;
         req.preset     = mtx::requests::Preset::PrivateChat;
         req.visibility = mtx::requests::Visibility::Private;
-        if (utils::localUser() != this->userId)
-                req.invite = {this->userId.toStdString()};
+        if (utils::localUser() != this->userid_)
+                req.invite = {this->userid_.toStdString()};
         emit ChatPage::instance()->createRoom(req);
 }
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 1725b96..38002ff 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -1,34 +1,92 @@
 #pragma once
 
+#include 
 #include 
 #include 
 #include 
 
 #include "MatrixClient.h"
 
-class DeviceInfo;
+namespace verification {
+Q_NAMESPACE
 
-class UserProfile : public QObject
+enum Status
+{
+        VERIFIED,
+        UNVERIFIED,
+        BLOCKED
+};
+Q_ENUM_NS(Status)
+}
+
+class DeviceInfo
+{
+public:
+        DeviceInfo(const QString deviceID,
+                   const QString displayName,
+                   verification::Status verification_status_)
+          : device_id(deviceID)
+          , display_name(displayName)
+          , verification_status(verification_status_)
+        {}
+        DeviceInfo()
+          : verification_status(verification::UNVERIFIED)
+        {}
+
+        QString device_id;
+        QString display_name;
+
+        verification::Status verification_status;
+};
+
+class DeviceInfoModel : public QAbstractListModel
 {
         Q_OBJECT
-        Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged)
-        Q_PROPERTY(std::vector deviceList READ getDeviceList NOTIFY deviceListUpdated)
 public:
-        // constructor
-        explicit UserProfile(QObject *parent = 0);
-        // getters
-        std::vector getDeviceList();
-        QString getUserId();
-        // setters
-        void setUserId(const QString &userId);
-
-        enum Status
+        enum Roles
         {
-                VERIFIED,
-                UNVERIFIED,
-                BLOCKED
+                DeviceId,
+                DeviceName,
+                VerificationStatus,
         };
-        Q_ENUM(Status)
+
+        explicit DeviceInfoModel(QObject *parent = nullptr)
+        {
+                (void)parent;
+                connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset);
+        };
+        QHash roleNames() const override;
+        int rowCount(const QModelIndex &parent = QModelIndex()) const
+        {
+                (void)parent;
+                return (int)deviceList_.size();
+        }
+        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+signals:
+        void queueReset(const std::vector &deviceList);
+public slots:
+        void reset(const std::vector &deviceList);
+
+private:
+        std::vector deviceList_;
+};
+
+class UserProfile : public QObject
+{
+        Q_OBJECT
+        Q_PROPERTY(QString displayName READ displayName CONSTANT)
+        Q_PROPERTY(QString userid READ userid CONSTANT)
+        Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
+        Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
+public:
+        UserProfile(QString roomid, QString userid, QObject *parent = 0);
+
+        DeviceInfoModel *deviceList();
+
+        QString userid();
+        QString displayName();
+        QString avatarUrl();
 
         void fetchDeviceList(const QString &userID);
         Q_INVOKABLE void banUser();
@@ -36,37 +94,13 @@ public:
         Q_INVOKABLE void kickUser();
         Q_INVOKABLE void startChat();
 
-signals:
-        void userIdChanged();
-        void deviceListUpdated();
-        void updateDeviceList();
-        void appendDeviceList(const QString device_id,
-                              const QString device_naem,
-                              const UserProfile::Status verification_status);
-
 private:
-        std::vector deviceList;
-        QString userId;
+        QString roomid_, userid_;
         std::optional cross_verified;
+        DeviceInfoModel deviceList_;
 
         void callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
                          std::string user_id,
                          std::optional> cross_verified);
 };
-
-class DeviceInfo
-{
-public:
-        DeviceInfo(const QString deviceID,
-                   const QString displayName,
-                   UserProfile::Status verification_status_)
-          : device_id(deviceID)
-          , display_name(displayName)
-          , verification_status(verification_status_)
-        {}
-
-        QString device_id;
-        QString display_name;
-        UserProfile::Status verification_status;
-};
\ No newline at end of file
diff --git a/src/ui/UserProfileModel.cpp b/src/ui/UserProfileModel.cpp
deleted file mode 100644
index 3fa8fe2..0000000
--- a/src/ui/UserProfileModel.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "UserProfileModel.h"
-#include 
-
-UserProfileModel::UserProfileModel(QObject *parent)
-  : QAbstractListModel(parent)
-  , deviceList(nullptr)
-{
-        this->deviceList = new UserProfile(this);
-
-        connect(this->deviceList, &UserProfile::userIdChanged, this, [this]() {
-                emit this->deviceList->updateDeviceList();
-        });
-        connect(this->deviceList, &UserProfile::deviceListUpdated, this, [this]() {
-                beginResetModel();
-                this->beginInsertRows(
-                  QModelIndex(), 0, this->deviceList->getDeviceList().size() - 1);
-                this->endInsertRows();
-                endResetModel();
-        });
-}
-
-int
-UserProfileModel::rowCount(const QModelIndex &parent) const
-{
-        if (parent.isValid() || !this->deviceList)
-                return 0;
-        return this->deviceList->getDeviceList().size();
-}
-
-QVariant
-UserProfileModel::data(const QModelIndex &index, int role) const
-{
-        if (!index.isValid() &&
-            static_cast(this->deviceList->getDeviceList().size()) <= index.row())
-                return QVariant();
-
-        const DeviceInfo device = this->deviceList->getDeviceList().at(index.row());
-        switch (role) {
-        case DEVICEID:
-                return QVariant(device.device_id);
-        case DISPLAYNAME:
-                return QVariant(device.display_name);
-        case VERIFIED_STATUS:
-                return device.verification_status;
-        }
-        return QVariant();
-}
-
-QHash
-UserProfileModel::roleNames() const
-{
-        QHash names;
-        names[DEVICEID]        = "deviceID";
-        names[DISPLAYNAME]     = "displayName";
-        names[VERIFIED_STATUS] = "verified_status";
-        return names;
-}
-
-UserProfile *
-UserProfileModel::getList() const
-{
-        return (this->deviceList);
-}
diff --git a/src/ui/UserProfileModel.h b/src/ui/UserProfileModel.h
deleted file mode 100644
index ba7a252..0000000
--- a/src/ui/UserProfileModel.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#pragma once
-
-#include "UserProfile.h"
-#include 
-
-class UserProfile; // forward declaration of the class UserProfile
-
-class UserProfileModel : public QAbstractListModel
-{
-        Q_OBJECT
-        Q_PROPERTY(UserProfile *deviceList READ getList)
-
-public:
-        explicit UserProfileModel(QObject *parent = nullptr);
-
-        enum
-        {
-                DEVICEID,
-                DISPLAYNAME,
-                VERIFIED_STATUS
-        };
-        UserProfile *getList() const;
-
-        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
-        QVariant data(const QModelIndex &index, int role) const override;
-        virtual QHash roleNames() const override;
-
-private:
-        UserProfile *deviceList;
-};
\ No newline at end of file

From 1103cc15cfe59b35e540855090af381b0f2e5f8e Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sun, 5 Jul 2020 21:33:27 +0530
Subject: [PATCH 26/70] Adding icons to UserProfile

---
 CMakeLists.txt                                |   3 -
 resources/qml/TimelineView.qml                |   2 +-
 resources/qml/UserProfile.qml                 |  81 ++++++-----
 .../DeviceVerification.qml                    |   4 +
 src/DeviceVerificationFlow.cpp                | 130 +++++++++++++-----
 src/DeviceVerificationFlow.h                  |   6 +
 src/timeline/TimelineViewManager.h            |   2 +
 src/ui/UserProfile.cpp                        |  11 +-
 src/ui/UserProfile.h                          |   4 +-
 9 files changed, 163 insertions(+), 80 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5ad8a62..2aff991 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,9 +15,6 @@ set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard")
 set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "Require C++ standard to be supported")
 set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default")
 
-# set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
-# set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
-
 option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
 include("cmake/HunterGate.cmake")
 HunterGate(
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index ec63487..699efc5 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -106,7 +106,7 @@ Page {
 		}
 		Connections {
 			target: TimelineManager
-			function onNewDeviceVerificationRequest(flow) {
+			function onNewDeviceVerificationRequest(flow,transactionId,userId,deviceId) {
 				flow.userId = userId;
 				flow.sender = false;
 				flow.deviceId = deviceId;
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index df54367..5bdccb4 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -17,6 +17,13 @@ ApplicationWindow{
 	Layout.alignment: Qt.AlignHCenter
 	palette: colors
 
+	Connections{
+		target: deviceVerificationList
+		onUpdateProfile: {
+			profile.fetchDeviceList(profile.userid)
+		}
+	}
+
 	Component {
 		id: deviceVerificationDialog
 		DeviceVerification {}
@@ -139,20 +146,12 @@ ApplicationWindow{
 							top : 50
 						}
 						ColumnLayout{
-							RowLayout{
-								Text{
-									Layout.fillWidth: true
-									color: colors.text
-									font.bold: true
-									Layout.alignment: Qt.AlignLeft
-									text: model.deviceId
-								}
-								Text{
-									Layout.fillWidth: true
-									color:colors.text
-									Layout.alignment: Qt.AlignLeft
-									text: (model.verificationStatus ==  VerificationStatus.VERIFIED?"V":(model.verificationStatus ==  VerificationStatus.UNVERIFIED?"NV":"B"))
-								}
+							Text{
+								Layout.fillWidth: true
+								color: colors.text
+								font.bold: true
+								Layout.alignment: Qt.AlignLeft
+								text: model.deviceId
 							}
 							Text{
 								Layout.fillWidth: true
@@ -161,27 +160,41 @@ ApplicationWindow{
 								text: model.deviceName
 							}
 						}
-						Button{
-							id: verifyButton
-							text:"Verify"
-							onClicked: {
-								var newFlow = deviceVerificationFlow.createObject(userProfileDialog,
-								{userId : profile.userid, sender: true, deviceId : model.deviceID});
-								deviceVerificationList.add(newFlow.tranId);
-								var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow});
-								dialog.show();
+						RowLayout{
+							Image{
+								Layout.preferredWidth: 20
+								Layout.preferredHeight: 20
+								source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green":
+								((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow":
+								"image://colorimage/:/icons/icons/ui/unlock.png?red"))
 							}
-							Layout.margins:{
-								right: 10
-							}
-							palette {
-								button: "white"
-							}
-							contentItem: Text {
-								text: verifyButton.text
-								color: "black"
-								horizontalAlignment: Text.AlignHCenter
-								verticalAlignment: Text.AlignVCenter
+							Button{
+								id: verifyButton
+								text:(model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify"
+								onClicked: {
+									var newFlow = deviceVerificationFlow.createObject(userProfileDialog,
+									{userId : profile.userid, sender: true, deviceId : model.deviceId});
+									if(model.verificationStatus == VerificationStatus.VERIFIED){
+										newFlow.unverify();
+										deviceVerificationList.updateProfile(newFlow.userId);
+									}else{
+										deviceVerificationList.add(newFlow.tranId);
+										var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow});
+										dialog.show();
+									}
+								}
+								Layout.margins:{
+									right: 10
+								}
+								palette {
+									button: "white"
+								}
+								contentItem: Text {
+									text: verifyButton.text
+									color: "black"
+									horizontalAlignment: Text.AlignHCenter
+									verticalAlignment: Text.AlignVCenter
+								}
 							}
 						}
 					}
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 15c2d7a..4d734a6 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -33,6 +33,10 @@ ApplicationWindow {
 			case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break;
 			case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break;
 		}
+
+		onRefreshProfile: {
+			deviceVerificationList.updateProfile(flow.userId);
+		}
 	}
 
 	Component {
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index b5134a3..7829c41 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -1,4 +1,5 @@
 #include "DeviceVerificationFlow.h"
+#include "Cache.h"
 #include "ChatPage.h"
 #include "Logging.h"
 
@@ -181,9 +182,9 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                                   // uncomment this in future to be compatible with the
                                   // MSC2366 this->sendVerificationDone(); and remove the
                                   // below line
-                                  if (this->isMacVerified == true)
-                                          emit this->deviceVerified();
-                                  else
+                                  if (this->isMacVerified == true) {
+                                          this->acceptDevice();
+                                  } else
                                           this->isMacVerified = true;
                           } else {
                                   this->cancelVerification(
@@ -208,7 +209,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
                         auto msg =
                           std::get>(message);
                         if (msg.content.transaction_id == this->transaction_id) {
-                                emit this->deviceVerified();
+                                this->acceptDevice();
                         }
                 });
         timeout->start(TIMEOUT);
@@ -259,36 +260,22 @@ DeviceVerificationFlow::setTransactionId(QString transaction_id_)
 void
 DeviceVerificationFlow::setUserId(QString userID)
 {
-        this->userId   = userID;
-        this->toClient = mtx::identifiers::parse(userID.toStdString());
-
-        mtx::responses::QueryKeys res;
-        mtx::requests::QueryKeys req;
-        req.device_keys[userID.toStdString()] = {};
-        http::client()->query_keys(
-          req,
-          [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
-                                                 mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to query device keys: {},{}",
-                                             err->matrix_error.errcode,
-                                             static_cast(err->status_code));
-                          return;
-                  }
-
-                  for (auto x : res.device_keys) {
-                          for (auto y : x.second) {
-                                  auto z = y.second;
-                                  if (z.user_id == user_id &&
-                                      z.device_id == this->deviceId.toStdString()) {
-                                          for (auto a : z.keys) {
-                                                  // TODO: Verify Signatures
-                                                  this->device_keys[a.first] = a.second;
-                                          }
-                                  }
-                          }
-                  }
-          });
+        this->userId    = userID;
+        this->toClient  = mtx::identifiers::parse(userID.toStdString());
+        auto user_cache = cache::getUserCache(userID.toStdString());
+
+        if (user_cache.has_value()) {
+                this->callback_fn(user_cache->keys, {}, userID.toStdString());
+        } else {
+                mtx::requests::QueryKeys req;
+                req.device_keys[userID.toStdString()] = {};
+                http::client()->query_keys(
+                  req,
+                  [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
+                                                         mtx::http::RequestErr err) {
+                          this->callback_fn(res, err, user_id);
+                  });
+        }
 }
 
 void
@@ -482,6 +469,16 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c
                             nhlog::net()->warn("failed to cancel verification request: {} {}",
                                                err->matrix_error.error,
                                                static_cast(err->status_code));
+                    auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
+                    if (verified_cache.has_value()) {
+                            verified_cache->device_blocked.push_back(this->deviceId.toStdString());
+                            cache::setVerifiedCache(this->userId.toStdString(),
+                                                    verified_cache.value());
+                    } else {
+                            cache::setVerifiedCache(
+                              this->userId.toStdString(),
+                              DeviceVerifiedCache{{}, {this->deviceId.toStdString()}});
+                    }
                     this->deleteLater();
             });
 }
@@ -546,7 +543,7 @@ DeviceVerificationFlow::sendVerificationMac()
                                                static_cast(err->status_code));
 
                     if (this->isMacVerified == true)
-                            emit this->deviceVerified();
+                            this->acceptDevice();
                     else
                             this->isMacVerified = true;
             });
@@ -555,8 +552,69 @@ DeviceVerificationFlow::sendVerificationMac()
 void
 DeviceVerificationFlow::acceptDevice()
 {
+        auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
+        if (verified_cache.has_value()) {
+                verified_cache->device_verified.push_back(this->deviceId.toStdString());
+                for (auto it = verified_cache->device_blocked.begin();
+                     it != verified_cache->device_blocked.end();
+                     it++) {
+                        if (*it == this->deviceId.toStdString()) {
+                                verified_cache->device_blocked.erase(it);
+                        }
+                }
+                cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value());
+        } else {
+                cache::setVerifiedCache(this->userId.toStdString(),
+                                        DeviceVerifiedCache{{this->deviceId.toStdString()}, {}});
+        }
+
         emit deviceVerified();
+        emit refreshProfile();
         this->deleteLater();
+}
+//! callback function to keep track of devices
+void
+DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res,
+                                    mtx::http::RequestErr err,
+                                    std::string user_id)
+{
+        if (err) {
+                nhlog::net()->warn("failed to query device keys: {},{}",
+                                   err->matrix_error.errcode,
+                                   static_cast(err->status_code));
+                return;
+        }
+
+        if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) {
+                nhlog::net()->warn("no devices retrieved {}", user_id);
+                return;
+        }
 
-        // Yet to add send to_device message
+        for (auto x : res.device_keys) {
+                for (auto y : x.second) {
+                        auto z = y.second;
+                        if (z.user_id == user_id && z.device_id == this->deviceId.toStdString()) {
+                                for (auto a : z.keys) {
+                                        // TODO: Verify Signatures
+                                        this->device_keys[a.first] = a.second;
+                                }
+                        }
+                }
+        }
+}
+
+void
+DeviceVerificationFlow::unverify()
+{
+        auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
+        if (verified_cache.has_value()) {
+                auto it = std::remove(verified_cache->device_verified.begin(),
+                                      verified_cache->device_verified.end(),
+                                      this->deviceId.toStdString());
+                verified_cache->device_verified.erase(it);
+                cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value());
+        }
+
+        emit refreshProfile();
+        this->deleteLater();
 }
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 891c6ae..edff7c8 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -51,6 +51,9 @@ public:
         void setDeviceId(QString deviceID);
         void setMethod(Method method_);
         void setSender(bool sender_);
+        void callback_fn(const mtx::responses::QueryKeys &res,
+                         mtx::http::RequestErr err,
+                         std::string user_id);
 
         nlohmann::json canonical_json;
 
@@ -73,12 +76,15 @@ public slots:
         void sendVerificationMac();
         //! Completes the verification flow
         void acceptDevice();
+        //! unverifies a device
+        void unverify();
 
 signals:
         void verificationRequestAccepted(Method method);
         void deviceVerified();
         void timedout();
         void verificationCanceled();
+        void refreshProfile();
 
 private:
         QString userId;
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index af8bc4b..a438ef5 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -29,6 +29,8 @@ public:
         Q_INVOKABLE void add(QString tran_id);
         Q_INVOKABLE void remove(QString tran_id);
         Q_INVOKABLE bool exist(QString tran_id);
+signals:
+        void updateProfile(QString userId);
 
 private:
         QVector deviceVerificationList;
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index fde0044..b4938e8 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -1,6 +1,7 @@
 #include "UserProfile.h"
 #include "Cache.h"
 #include "ChatPage.h"
+#include "DeviceVerificationFlow.h"
 #include "Logging.h"
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
@@ -122,10 +123,10 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
                    verified});
         }
 
-        // std::sort(
-        //   deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) {
-        //           return a.device_id > b.device_id;
-        //   });
+        std::sort(
+          deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) {
+                  return a.device_id > b.device_id;
+          });
 
         this->deviceList_.queueReset(std::move(deviceInfo));
 }
@@ -176,4 +177,4 @@ UserProfile::startChat()
         if (utils::localUser() != this->userid_)
                 req.invite = {this->userid_.toStdString()};
         emit ChatPage::instance()->createRoom(req);
-}
+}
\ No newline at end of file
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 38002ff..99c6a75 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -19,6 +19,8 @@ enum Status
 Q_ENUM_NS(Status)
 }
 
+class DeviceVerificationFlow;
+
 class DeviceInfo
 {
 public:
@@ -88,7 +90,7 @@ public:
         QString displayName();
         QString avatarUrl();
 
-        void fetchDeviceList(const QString &userID);
+        Q_INVOKABLE void fetchDeviceList(const QString &userID);
         Q_INVOKABLE void banUser();
         // Q_INVOKABLE void ignoreUser();
         Q_INVOKABLE void kickUser();

From a2979c2df1b2059e2e8a969f5c1a3a804bd2550c Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Mon, 6 Jul 2020 21:32:21 +0530
Subject: [PATCH 27/70] Updating keys of outdated encrypted users

---
 src/Cache.cpp                  | 56 ++++++++++++++++++++++++++++------
 src/Cache.h                    |  3 ++
 src/CacheCryptoStructs.h       | 29 +++++++++++++++---
 src/Cache_p.h                  |  5 +--
 src/DeviceVerificationFlow.cpp |  8 ++---
 src/ui/UserProfile.cpp         | 17 +++++------
 src/ui/UserProfile.h           |  3 +-
 7 files changed, 89 insertions(+), 32 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 553d1e9..1008277 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -31,8 +31,10 @@
 
 #include "Cache.h"
 #include "Cache_p.h"
+#include "ChatPage.h"
 #include "EventAccessors.h"
 #include "Logging.h"
+#include "MatrixClient.h"
 #include "Utils.h"
 
 //! Should be changed when a breaking change occurs in the cache format.
@@ -1009,6 +1011,8 @@ Cache::saveState(const mtx::responses::Sync &res)
 
         savePresence(txn, res.presence);
 
+        updateUserCache(res.device_lists);
+
         removeLeftRooms(txn, res.rooms.leave);
 
         txn.commit();
@@ -2885,17 +2889,13 @@ Cache::statusMessage(const std::string &user_id)
 void
 to_json(json &j, const UserCache &info)
 {
-        j["is_user_verified"] = info.is_user_verified;
-        j["cross_verified"]   = info.cross_verified;
-        j["keys"]             = info.keys;
+        j["keys"] = info.keys;
 }
 
 void
 from_json(const json &j, UserCache &info)
 {
-        info.is_user_verified = j.at("is_user_verified");
-        info.cross_verified   = j.at("cross_verified").get>();
-        info.keys             = j.at("keys").get();
+        info.keys = j.at("keys").get();
 }
 
 std::optional
@@ -2932,6 +2932,32 @@ Cache::setUserCache(const std::string &user_id, const UserCache &body)
         return res;
 }
 
+void
+Cache::updateUserCache(const mtx::responses::DeviceLists body)
+{
+        for (auto user_id : body.changed) {
+                mtx::requests::QueryKeys req;
+                req.device_keys[user_id] = {};
+
+                http::client()->query_keys(
+                  req,
+                  [user_id, this](const mtx::responses::QueryKeys res, mtx::http::RequestErr err) {
+                          if (err) {
+                                  nhlog::net()->warn("failed to query device keys: {},{}",
+                                                     err->matrix_error.errcode,
+                                                     static_cast(err->status_code));
+                                  return;
+                          }
+
+                          setUserCache(user_id, UserCache{std::move(res)});
+                  });
+        }
+
+        for (std::string user_id : body.left) {
+                deleteUserCache(user_id);
+        }
+}
+
 int
 Cache::deleteUserCache(const std::string &user_id)
 {
@@ -2947,15 +2973,19 @@ Cache::deleteUserCache(const std::string &user_id)
 void
 to_json(json &j, const DeviceVerifiedCache &info)
 {
-        j["device_verified"] = info.device_verified;
-        j["device_blocked"]  = info.device_blocked;
+        j["is_user_verified"] = info.is_user_verified;
+        j["cross_verified"]   = info.cross_verified;
+        j["device_verified"]  = info.device_verified;
+        j["device_blocked"]   = info.device_blocked;
 }
 
 void
 from_json(const json &j, DeviceVerifiedCache &info)
 {
-        info.device_verified = j.at("device_verified").get>();
-        info.device_blocked  = j.at("device_blocked").get>();
+        info.is_user_verified = j.at("is_user_verified");
+        info.cross_verified   = j.at("cross_verified").get>();
+        info.device_verified  = j.at("device_verified").get>();
+        info.device_blocked   = j.at("device_blocked").get>();
 }
 
 std::optional
@@ -3178,6 +3208,12 @@ getUserCache(const std::string &user_id)
         return instance_->getUserCache(user_id);
 }
 
+void
+updateUserCache(const mtx::responses::DeviceLists body)
+{
+        instance_->updateUserCache(body);
+}
+
 int
 setUserCache(const std::string &user_id, const UserCache &body)
 {
diff --git a/src/Cache.h b/src/Cache.h
index 0c955c9..82d909a 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -64,6 +64,9 @@ statusMessage(const std::string &user_id);
 std::optional
 getUserCache(const std::string &user_id);
 
+void
+updateUserCache(const mtx::responses::DeviceLists body);
+
 int
 setUserCache(const std::string &user_id, const UserCache &body);
 
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 241cac7..ba746f5 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -66,14 +66,16 @@ struct OlmSessionStorage
         std::mutex group_inbound_mtx;
 };
 
+// this will store the keys of the user with whom a encrypted room is shared with
 struct UserCache
 {
-        //! this stores if the user is verified (with cross-signing)
-        bool is_user_verified = false;
-        //! list of verified device_ids with cross-signing
-        std::vector cross_verified;
         //! map of public key key_ids and their public_key
         mtx::responses::QueryKeys keys;
+
+        UserCache(mtx::responses::QueryKeys res)
+          : keys(res)
+        {}
+        UserCache() {}
 };
 
 void
@@ -81,11 +83,30 @@ to_json(nlohmann::json &j, const UserCache &info);
 void
 from_json(const nlohmann::json &j, UserCache &info);
 
+// the reason these are stored in a seperate cache rather than storing it in the user cache is
+// UserCache stores only keys of users with which encrypted room is shared
 struct DeviceVerifiedCache
 {
         //! list of verified device_ids with device-verification
         std::vector device_verified;
+        //! list of verified device_ids with cross-signing
+        std::vector cross_verified;
+        //! list of devices the user blocks
         std::vector device_blocked;
+        //! this stores if the user is verified (with cross-signing)
+        bool is_user_verified = false;
+
+        DeviceVerifiedCache(std::vector device_verified_,
+                            std::vector cross_verified_,
+                            std::vector device_blocked_,
+                            bool is_user_verified_ = false)
+          : device_verified(device_verified_)
+          , cross_verified(cross_verified_)
+          , device_blocked(device_blocked_)
+          , is_user_verified(is_user_verified_)
+        {}
+
+        DeviceVerifiedCache() {}
 };
 
 void
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 60fa930..f75b0f4 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -56,6 +56,7 @@ public:
 
         // user cache stores user keys
         std::optional getUserCache(const std::string &user_id);
+        void updateUserCache(const mtx::responses::DeviceLists body);
         int setUserCache(const std::string &user_id, const UserCache &body);
         int deleteUserCache(const std::string &user_id);
 
@@ -521,12 +522,12 @@ private:
 
         lmdb::dbi getUserCacheDb(lmdb::txn &txn)
         {
-                return lmdb::dbi::open(txn, std::string("user_cache").c_str(), MDB_CREATE);
+                return lmdb::dbi::open(txn, "user_cache", MDB_CREATE);
         }
 
         lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn)
         {
-                return lmdb::dbi::open(txn, std::string("verified").c_str(), MDB_CREATE);
+                return lmdb::dbi::open(txn, "verified", MDB_CREATE);
         }
 
         //! Retrieves or creates the database that stores the open OLM sessions between our device
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 7829c41..0122e69 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -477,7 +477,7 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c
                     } else {
                             cache::setVerifiedCache(
                               this->userId.toStdString(),
-                              DeviceVerifiedCache{{}, {this->deviceId.toStdString()}});
+                              DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}});
                     }
                     this->deleteLater();
             });
@@ -564,8 +564,9 @@ DeviceVerificationFlow::acceptDevice()
                 }
                 cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value());
         } else {
-                cache::setVerifiedCache(this->userId.toStdString(),
-                                        DeviceVerifiedCache{{this->deviceId.toStdString()}, {}});
+                cache::setVerifiedCache(
+                  this->userId.toStdString(),
+                  DeviceVerifiedCache{{this->deviceId.toStdString()}, {}, {}});
         }
 
         emit deviceVerified();
@@ -616,5 +617,4 @@ DeviceVerificationFlow::unverify()
         }
 
         emit refreshProfile();
-        this->deleteLater();
 }
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index b4938e8..6ae04d0 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -77,8 +77,7 @@ UserProfile::avatarUrl()
 void
 UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
-                         std::string user_id,
-                         std::optional> cross_verified)
+                         std::string user_id)
 {
         if (err) {
                 nhlog::net()->warn("failed to query device keys: {},{}",
@@ -101,16 +100,15 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
 
                 // TODO: Verify signatures and ignore those that don't pass.
                 verification::Status verified = verification::Status::UNVERIFIED;
-                if (cross_verified.has_value()) {
-                        if (std::find(cross_verified->begin(), cross_verified->end(), d.first) !=
-                            cross_verified->end())
+                if (device_verified.has_value()) {
+                        if (std::find(device_verified->cross_verified.begin(),
+                                      device_verified->cross_verified.end(),
+                                      d.first) != device_verified->cross_verified.end())
                                 verified = verification::Status::VERIFIED;
-                } else if (device_verified.has_value()) {
                         if (std::find(device_verified->device_verified.begin(),
                                       device_verified->device_verified.end(),
                                       d.first) != device_verified->device_verified.end())
                                 verified = verification::Status::VERIFIED;
-                } else if (device_verified.has_value()) {
                         if (std::find(device_verified->device_blocked.begin(),
                                       device_verified->device_blocked.end(),
                                       d.first) != device_verified->device_blocked.end())
@@ -138,8 +136,7 @@ UserProfile::fetchDeviceList(const QString &userID)
         auto user_cache = cache::getUserCache(userID.toStdString());
 
         if (user_cache.has_value()) {
-                this->callback_fn(
-                  user_cache->keys, {}, userID.toStdString(), user_cache->cross_verified);
+                this->callback_fn(user_cache->keys, {}, userID.toStdString());
         } else {
                 mtx::requests::QueryKeys req;
                 req.device_keys[userID.toStdString()] = {};
@@ -147,7 +144,7 @@ UserProfile::fetchDeviceList(const QString &userID)
                   req,
                   [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
                                                          mtx::http::RequestErr err) {
-                          this->callback_fn(res, err, user_id, {});
+                          this->callback_fn(res, err, user_id);
                   });
         }
 }
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 99c6a75..4e04840 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -103,6 +103,5 @@ private:
 
         void callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
-                         std::string user_id,
-                         std::optional> cross_verified);
+                         std::string user_id);
 };

From 1fcd768f88f7e84978d19283c9fa6205624f2544 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sat, 18 Jul 2020 01:46:30 +0530
Subject: [PATCH 28/70] Adding Room Key Verification Stuff

---
 resources/qml/UserProfile.qml               |  22 +-
 resources/qml/delegates/MessageDelegate.qml |  54 ++
 src/Cache.cpp                               |   2 +-
 src/ChatPage.h                              |  18 +-
 src/DeviceVerificationFlow.cpp              | 665 +++++++++++---------
 src/DeviceVerificationFlow.h                |  17 +-
 src/EventAccessors.cpp                      |  11 +-
 src/Olm.cpp                                 |  39 +-
 src/timeline/TimelineModel.cpp              | 187 +++++-
 src/timeline/TimelineModel.h                |   8 +
 src/timeline/TimelineViewManager.cpp        |  76 +--
 src/timeline/TimelineViewManager.h          |   9 +-
 src/ui/UserProfile.cpp                      |  34 +
 src/ui/UserProfile.h                        |   4 +
 14 files changed, 786 insertions(+), 360 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 5bdccb4..c7dbc9a 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -74,6 +74,26 @@ ApplicationWindow{
 				Layout.alignment: Qt.AlignHCenter
 			}
 
+			Button {
+				id: verifyUserButton
+				text: "Verify"
+				Layout.alignment: Qt.AlignHCenter
+				enabled: profile.isUserVerified?false:true
+				visible: profile.isUserVerified?false:true
+				palette {
+					button: "white"
+				}
+				contentItem: Text {
+					text: verifyUserButton.text
+					color: "black"
+					horizontalAlignment: Text.AlignHCenter
+					verticalAlignment: Text.AlignVCenter
+				}
+				onClicked: {
+					profile.verifyUser();
+				}
+			}
+
 			RowLayout {
 				Layout.alignment: Qt.AlignHCenter
 				ImageButton {
@@ -127,7 +147,7 @@ ApplicationWindow{
 			}
 
 			ScrollView {
-				implicitHeight: userProfileDialog.height/2 + 20
+				implicitHeight: userProfileDialog.height/2-13
 				implicitWidth: userProfileDialog.width-20
 				clip: true
 				Layout.alignment: Qt.AlignHCenter
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 6f69f02..c556a97 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -127,6 +127,60 @@ Item {
 				text: TimelineManager.timeline.formatMemberEvent(model.data.id);
 			}
 		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationRequest
+			NoticeMessage {
+				text: "KeyVerificationRequest";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationStart
+			NoticeMessage {
+				text: "KeyVerificationStart";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationReady
+			NoticeMessage {
+				text: "KeyVerificationReady";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationCancel
+			NoticeMessage {
+				text: "KeyVerificationCancel";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationKey
+			NoticeMessage {
+				text: "KeyVerificationKey";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationMac
+			NoticeMessage {
+				text: "KeyVerificationMac";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationDone
+			NoticeMessage {
+				text: "KeyVerificationDone";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationDone
+			NoticeMessage {
+				text: "KeyVerificationDone";
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.KeyVerificationAccept
+			NoticeMessage {
+				text: "KeyVerificationAccept";
+			}
+		}
 		DelegateChoice {
 			Placeholder {}
 		}
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 1008277..8cee345 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1011,7 +1011,7 @@ Cache::saveState(const mtx::responses::Sync &res)
 
         savePresence(txn, res.presence);
 
-        updateUserCache(res.device_lists);
+        // updateUserCache(res.device_lists);
 
         removeLeftRooms(txn, res.rooms.leave);
 
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 172f74f..0e7c889 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -167,16 +167,18 @@ signals:
 
         //! Signals for device verificaiton
         void recievedDeviceVerificationAccept(
-          const mtx::events::collections::DeviceEvents &message);
+          const mtx::events::msg::KeyVerificationAccept &message);
         void recievedDeviceVerificationRequest(
-          const mtx::events::collections::DeviceEvents &message);
+          const mtx::events::msg::KeyVerificationRequest &message,
+          std::string sender);
         void recievedDeviceVerificationCancel(
-          const mtx::events::collections::DeviceEvents &message);
-        void recievedDeviceVerificationKey(const mtx::events::collections::DeviceEvents &message);
-        void recievedDeviceVerificationMac(const mtx::events::collections::DeviceEvents &message);
-        void recievedDeviceVerificationStart(const mtx::events::collections::DeviceEvents &message);
-        void recievedDeviceVerificationReady(const mtx::events::collections::DeviceEvents &message);
-        void recievedDeviceVerificationDone(const mtx::events::collections::DeviceEvents &message);
+          const mtx::events::msg::KeyVerificationCancel &message);
+        void recievedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
+        void recievedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
+        void recievedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
+                                             std::string sender);
+        void recievedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
+        void recievedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
 
 private slots:
         void showUnreadMessageNotification(int count);
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 0122e69..69de493 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -6,11 +6,13 @@
 #include 
 #include 
 
+#include 
+
 static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
 
 namespace msgs = mtx::events::msg;
 
-DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
+DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type)
 {
         timeout = new QTimer(this);
         timeout->setSingleShot(true);
@@ -26,192 +28,218 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *)
           ChatPage::instance(),
           &ChatPage::recievedDeviceVerificationStart,
           this,
-          [this](const mtx::events::collections::DeviceEvents &message) {
-                  auto msg =
-                    std::get>(message);
-                  if (msg.content.transaction_id == this->transaction_id) {
-                          if ((std::find(msg.content.key_agreement_protocols.begin(),
-                                         msg.content.key_agreement_protocols.end(),
-                                         "curve25519-hkdf-sha256") !=
-                               msg.content.key_agreement_protocols.end()) &&
-                              (std::find(msg.content.hashes.begin(),
-                                         msg.content.hashes.end(),
-                                         "sha256") != msg.content.hashes.end()) &&
-                              (std::find(msg.content.message_authentication_codes.begin(),
-                                         msg.content.message_authentication_codes.end(),
-                                         "hmac-sha256") !=
-                               msg.content.message_authentication_codes.end())) {
-                                  if (std::find(msg.content.short_authentication_string.begin(),
-                                                msg.content.short_authentication_string.end(),
-                                                mtx::events::msg::SASMethods::Decimal) !=
-                                      msg.content.short_authentication_string.end()) {
-                                          this->method = DeviceVerificationFlow::Method::Emoji;
-                                  } else if (std::find(
-                                               msg.content.short_authentication_string.begin(),
-                                               msg.content.short_authentication_string.end(),
-                                               mtx::events::msg::SASMethods::Emoji) !=
-                                             msg.content.short_authentication_string.end()) {
-                                          this->method = DeviceVerificationFlow::Method::Decimal;
-                                  } else {
-                                          this->cancelVerification(
-                                            DeviceVerificationFlow::Error::UnknownMethod);
-                                          return;
-                                  }
-                                  this->acceptVerificationRequest();
-                                  this->canonical_json = nlohmann::json(msg.content);
-                          } else {
-                                  this->cancelVerification(
-                                    DeviceVerificationFlow::Error::UnknownMethod);
-                          }
+          [this](const mtx::events::msg::KeyVerificationStart &msg, std::string) {
+                  if (msg.transaction_id.has_value()) {
+                          if (msg.transaction_id.value() != this->transaction_id)
+                                  return;
+                  } else if (msg.relates_to.has_value()) {
+                          if (msg.relates_to.value().in_reply_to.event_id !=
+                              this->relation.in_reply_to.event_id)
+                                  return;
                   }
-          });
-        connect(
-          ChatPage::instance(),
-          &ChatPage::recievedDeviceVerificationAccept,
-          this,
-          [this](const mtx::events::collections::DeviceEvents &message) {
-                  auto msg =
-                    std::get>(message);
-                  if (msg.content.transaction_id == this->transaction_id) {
-                          if ((msg.content.key_agreement_protocol == "curve25519-hkdf-sha256") &&
-                              (msg.content.hash == "sha256") &&
-                              (msg.content.message_authentication_code == "hkdf-hmac-sha256")) {
-                                  this->commitment = msg.content.commitment;
-                                  if (std::find(msg.content.short_authentication_string.begin(),
-                                                msg.content.short_authentication_string.end(),
-                                                mtx::events::msg::SASMethods::Emoji) !=
-                                      msg.content.short_authentication_string.end()) {
-                                          this->method = DeviceVerificationFlow::Method::Emoji;
-                                  } else {
-                                          this->method = DeviceVerificationFlow::Method::Decimal;
-                                  }
-                                  this->mac_method = msg.content.message_authentication_code;
-                                  this->sendVerificationKey();
+                  if ((std::find(msg.key_agreement_protocols.begin(),
+                                 msg.key_agreement_protocols.end(),
+                                 "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) &&
+                      (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") !=
+                       msg.hashes.end()) &&
+                      (std::find(msg.message_authentication_codes.begin(),
+                                 msg.message_authentication_codes.end(),
+                                 "hmac-sha256") != msg.message_authentication_codes.end())) {
+                          if (std::find(msg.short_authentication_string.begin(),
+                                        msg.short_authentication_string.end(),
+                                        mtx::events::msg::SASMethods::Decimal) !=
+                              msg.short_authentication_string.end()) {
+                                  this->method = DeviceVerificationFlow::Method::Emoji;
+                          } else if (std::find(msg.short_authentication_string.begin(),
+                                               msg.short_authentication_string.end(),
+                                               mtx::events::msg::SASMethods::Emoji) !=
+                                     msg.short_authentication_string.end()) {
+                                  this->method = DeviceVerificationFlow::Method::Decimal;
                           } else {
                                   this->cancelVerification(
                                     DeviceVerificationFlow::Error::UnknownMethod);
+                                  return;
                           }
+                          this->acceptVerificationRequest();
+                          this->canonical_json = nlohmann::json(msg);
+                  } else {
+                          this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
                   }
           });
+
+        connect(ChatPage::instance(),
+                &ChatPage::recievedDeviceVerificationAccept,
+                this,
+                [this](const mtx::events::msg::KeyVerificationAccept &msg) {
+                        if (msg.transaction_id.has_value()) {
+                                if (msg.transaction_id.value() != this->transaction_id)
+                                        return;
+                        } else if (msg.relates_to.has_value()) {
+                                if (msg.relates_to.value().in_reply_to.event_id !=
+                                    this->relation.in_reply_to.event_id)
+                                        return;
+                        }
+                        if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
+                            (msg.hash == "sha256") &&
+                            (msg.message_authentication_code == "hkdf-hmac-sha256")) {
+                                this->commitment = msg.commitment;
+                                if (std::find(msg.short_authentication_string.begin(),
+                                              msg.short_authentication_string.end(),
+                                              mtx::events::msg::SASMethods::Emoji) !=
+                                    msg.short_authentication_string.end()) {
+                                        this->method = DeviceVerificationFlow::Method::Emoji;
+                                } else {
+                                        this->method = DeviceVerificationFlow::Method::Decimal;
+                                }
+                                this->mac_method = msg.message_authentication_code;
+                                this->sendVerificationKey();
+                        } else {
+                                this->cancelVerification(
+                                  DeviceVerificationFlow::Error::UnknownMethod);
+                        }
+                });
+
         connect(ChatPage::instance(),
                 &ChatPage::recievedDeviceVerificationCancel,
                 this,
-                [this](const mtx::events::collections::DeviceEvents &message) {
-                        auto msg =
-                          std::get>(message);
-                        if (msg.content.transaction_id == this->transaction_id) {
-                                emit verificationCanceled();
+                [this](const mtx::events::msg::KeyVerificationCancel &msg) {
+                        if (msg.transaction_id.has_value()) {
+                                if (msg.transaction_id.value() != this->transaction_id)
+                                        return;
+                        } else if (msg.relates_to.has_value()) {
+                                if (msg.relates_to.value().in_reply_to.event_id !=
+                                    this->relation.in_reply_to.event_id)
+                                        return;
+                        }
+                        emit verificationCanceled();
+                });
+
+        connect(ChatPage::instance(),
+                &ChatPage::recievedDeviceVerificationKey,
+                this,
+                [this](const mtx::events::msg::KeyVerificationKey &msg) {
+                        if (msg.transaction_id.has_value()) {
+                                if (msg.transaction_id.value() != this->transaction_id)
+                                        return;
+                        } else if (msg.relates_to.has_value()) {
+                                if (msg.relates_to.value().in_reply_to.event_id !=
+                                    this->relation.in_reply_to.event_id)
+                                        return;
+                        }
+                        this->sas->set_their_key(msg.key);
+                        std::string info;
+                        if (this->sender == true) {
+                                info = "MATRIX_KEY_VERIFICATION_SAS|" +
+                                       http::client()->user_id().to_string() + "|" +
+                                       http::client()->device_id() + "|" + this->sas->public_key() +
+                                       "|" + this->toClient.to_string() + "|" +
+                                       this->deviceId.toStdString() + "|" + msg.key + "|" +
+                                       this->transaction_id;
+                        } else {
+                                info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() +
+                                       "|" + this->deviceId.toStdString() + "|" + msg.key + "|" +
+                                       http::client()->user_id().to_string() + "|" +
+                                       http::client()->device_id() + "|" + this->sas->public_key() +
+                                       "|" + this->transaction_id;
+                        }
+
+                        if (this->method == DeviceVerificationFlow::Method::Emoji) {
+                                this->sasList = this->sas->generate_bytes_emoji(info);
+                        } else if (this->method == DeviceVerificationFlow::Method::Decimal) {
+                                this->sasList = this->sas->generate_bytes_decimal(info);
+                        }
+                        if (this->sender == false) {
+                                emit this->verificationRequestAccepted(this->method);
+                                this->sendVerificationKey();
+                        } else {
+                                if (this->commitment ==
+                                    mtx::crypto::bin2base64_unpadded(
+                                      mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) {
+                                        emit this->verificationRequestAccepted(this->method);
+                                } else {
+                                        this->cancelVerification(
+                                          DeviceVerificationFlow::Error::MismatchedCommitment);
+                                }
                         }
                 });
+
         connect(
           ChatPage::instance(),
-          &ChatPage::recievedDeviceVerificationKey,
+          &ChatPage::recievedDeviceVerificationMac,
           this,
-          [this](const mtx::events::collections::DeviceEvents &message) {
-                  auto msg = std::get>(message);
-                  if (msg.content.transaction_id == this->transaction_id) {
-                          this->sas->set_their_key(msg.content.key);
-                          std::string info;
-                          if (this->sender == true) {
-                                  info = "MATRIX_KEY_VERIFICATION_SAS|" +
-                                         http::client()->user_id().to_string() + "|" +
-                                         http::client()->device_id() + "|" +
-                                         this->sas->public_key() + "|" +
-                                         this->toClient.to_string() + "|" +
-                                         this->deviceId.toStdString() + "|" + msg.content.key +
-                                         "|" + this->transaction_id;
-                          } else {
-                                  info = "MATRIX_KEY_VERIFICATION_SAS|" +
-                                         this->toClient.to_string() + "|" +
-                                         this->deviceId.toStdString() + "|" + msg.content.key +
-                                         "|" + http::client()->user_id().to_string() + "|" +
-                                         http::client()->device_id() + "|" +
-                                         this->sas->public_key() + "|" + this->transaction_id;
-                          }
-
-                          if (this->method == DeviceVerificationFlow::Method::Emoji) {
-                                  this->sasList = this->sas->generate_bytes_emoji(info);
-                          } else if (this->method == DeviceVerificationFlow::Method::Decimal) {
-                                  this->sasList = this->sas->generate_bytes_decimal(info);
-                          }
-                          if (this->sender == false) {
-                                  emit this->verificationRequestAccepted(this->method);
-                                  this->sendVerificationKey();
-                          } else {
-                                  if (this->commitment ==
-                                      mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256(
-                                        msg.content.key + this->canonical_json.dump()))) {
-                                          emit this->verificationRequestAccepted(this->method);
+          [this](const mtx::events::msg::KeyVerificationMac &msg) {
+                  if (msg.transaction_id.has_value()) {
+                          if (msg.transaction_id.value() != this->transaction_id)
+                                  return;
+                  } else if (msg.relates_to.has_value()) {
+                          if (msg.relates_to.value().in_reply_to.event_id !=
+                              this->relation.in_reply_to.event_id)
+                                  return;
+                  }
+                  std::string info = "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() +
+                                     this->deviceId.toStdString() +
+                                     http::client()->user_id().to_string() +
+                                     http::client()->device_id() + this->transaction_id;
+
+                  std::vector key_list;
+                  std::string key_string;
+                  for (auto mac : msg.mac) {
+                          key_string += mac.first + ",";
+                          if (device_keys[mac.first] != "") {
+                                  if (mac.second ==
+                                      this->sas->calculate_mac(this->device_keys[mac.first],
+                                                               info + mac.first)) {
                                   } else {
                                           this->cancelVerification(
-                                            DeviceVerificationFlow::Error::MismatchedCommitment);
+                                            DeviceVerificationFlow::Error::KeyMismatch);
+                                          return;
                                   }
                           }
                   }
-          });
-        connect(
-          ChatPage::instance(),
-          &ChatPage::recievedDeviceVerificationMac,
-          this,
-          [this](const mtx::events::collections::DeviceEvents &message) {
-                  auto msg = std::get>(message);
-                  if (msg.content.transaction_id == this->transaction_id) {
-                          std::string info =
-                            "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() +
-                            this->deviceId.toStdString() + http::client()->user_id().to_string() +
-                            http::client()->device_id() + this->transaction_id;
-
-                          std::vector key_list;
-                          std::string key_string;
-                          for (auto mac : msg.content.mac) {
-                                  key_string += mac.first + ",";
-                                  if (device_keys[mac.first] != "") {
-                                          if (mac.second ==
-                                              this->sas->calculate_mac(this->device_keys[mac.first],
-                                                                       info + mac.first)) {
-                                          } else {
-                                                  this->cancelVerification(
-                                                    DeviceVerificationFlow::Error::KeyMismatch);
-                                                  return;
-                                          }
-                                  }
-                          }
-                          key_string = key_string.substr(0, key_string.length() - 1);
-                          if (msg.content.keys ==
-                              this->sas->calculate_mac(key_string, info + "KEY_IDS")) {
-                                  // uncomment this in future to be compatible with the
-                                  // MSC2366 this->sendVerificationDone(); and remove the
-                                  // below line
-                                  if (this->isMacVerified == true) {
-                                          this->acceptDevice();
-                                  } else
-                                          this->isMacVerified = true;
-                          } else {
-                                  this->cancelVerification(
-                                    DeviceVerificationFlow::Error::KeyMismatch);
-                          }
+                  key_string = key_string.substr(0, key_string.length() - 1);
+                  if (msg.keys == this->sas->calculate_mac(key_string, info + "KEY_IDS")) {
+                          // uncomment this in future to be compatible with the
+                          // MSC2366 this->sendVerificationDone(); and remove the
+                          // below line
+                          if (this->isMacVerified == true) {
+                                  this->acceptDevice();
+                          } else
+                                  this->isMacVerified = true;
+                  } else {
+                          this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
                   }
           });
+
         connect(ChatPage::instance(),
                 &ChatPage::recievedDeviceVerificationReady,
                 this,
-                [this](const mtx::events::collections::DeviceEvents &message) {
-                        auto msg =
-                          std::get>(message);
-                        if (msg.content.transaction_id == this->transaction_id) {
-                                this->startVerificationRequest();
+                [this](const mtx::events::msg::KeyVerificationReady &msg) {
+                        if (msg.transaction_id.has_value()) {
+                                if (msg.transaction_id.value() != this->transaction_id)
+                                        return;
+                        } else if (msg.relates_to.has_value()) {
+                                if (msg.relates_to.value().in_reply_to.event_id !=
+                                    this->relation.in_reply_to.event_id)
+                                        return;
                         }
+                        this->startVerificationRequest();
                 });
+
         connect(ChatPage::instance(),
                 &ChatPage::recievedDeviceVerificationDone,
                 this,
-                [this](const mtx::events::collections::DeviceEvents &message) {
-                        auto msg =
-                          std::get>(message);
-                        if (msg.content.transaction_id == this->transaction_id) {
-                                this->acceptDevice();
+                [this](const mtx::events::msg::KeyVerificationDone &msg) {
+                        if (msg.transaction_id.has_value()) {
+                                if (msg.transaction_id.value() != this->transaction_id)
+                                        return;
+                        } else if (msg.relates_to.has_value()) {
+                                if (msg.relates_to.value().in_reply_to.event_id !=
+                                    this->relation.in_reply_to.event_id)
+                                        return;
                         }
+                        this->acceptDevice();
                 });
+
         timeout->start(TIMEOUT);
 }
 
@@ -294,18 +322,18 @@ void
 DeviceVerificationFlow::setSender(bool sender_)
 {
         this->sender = sender_;
-        if (this->sender == true)
+        if (this->sender == true && this->type == DeviceVerificationFlow::Type::ToDevice)
                 this->transaction_id = http::client()->generate_txn_id();
+        else if (this->sender == true && this->type == DeviceVerificationFlow::Type::RoomMsg)
+                this->relation.in_reply_to.event_id = http::client()->generate_txn_id();
 }
 
 //! accepts a verification
 void
 DeviceVerificationFlow::acceptVerificationRequest()
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationAccept req;
 
-        req.transaction_id              = this->transaction_id;
         req.method                      = mtx::events::msg::VerificationMethods::SASv1;
         req.key_agreement_protocol      = "curve25519-hkdf-sha256";
         req.hash                        = "sha256";
@@ -317,126 +345,152 @@ DeviceVerificationFlow::acceptVerificationRequest()
         req.commitment = mtx::crypto::bin2base64_unpadded(
           mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump()));
 
-        body[this->toClient][this->deviceId.toStdString()] = req;
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to accept verification request: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-            });
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                mtx::requests::ToDeviceMessages body;
+                req.transaction_id = this->transaction_id;
+
+                body[this->toClient][this->deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn(
+                                      "failed to accept verification request: {} {}",
+                                      err->matrix_error.error,
+                                      static_cast(err->status_code));
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
 }
 //! responds verification request
 void
 DeviceVerificationFlow::sendVerificationReady()
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationReady req;
 
-        req.from_device    = http::client()->device_id();
-        req.transaction_id = this->transaction_id;
-        req.methods        = {mtx::events::msg::VerificationMethods::SASv1};
-
-        body[this->toClient][this->deviceId.toStdString()] = req;
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to send verification ready: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-            });
+        req.from_device = http::client()->device_id();
+        req.methods     = {mtx::events::msg::VerificationMethods::SASv1};
+
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                req.transaction_id = this->transaction_id;
+                mtx::requests::ToDeviceMessages body;
+
+                body[this->toClient][this->deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn("failed to send verification ready: {} {}",
+                                                       err->matrix_error.error,
+                                                       static_cast(err->status_code));
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
 }
 //! accepts a verification
 void
 DeviceVerificationFlow::sendVerificationDone()
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationDone req;
 
-        req.transaction_id = this->transaction_id;
-
-        body[this->toClient][this->deviceId.toStdString()] = req;
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to send verification done: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-            });
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                mtx::requests::ToDeviceMessages body;
+                req.transaction_id = this->transaction_id;
+
+                body[this->toClient][this->deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn("failed to send verification done: {} {}",
+                                                       err->matrix_error.error,
+                                                       static_cast(err->status_code));
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
 }
 //! starts the verification flow
 void
 DeviceVerificationFlow::startVerificationRequest()
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationStart req;
 
         req.from_device                  = http::client()->device_id();
-        req.transaction_id               = this->transaction_id;
         req.method                       = mtx::events::msg::VerificationMethods::SASv1;
         req.key_agreement_protocols      = {"curve25519-hkdf-sha256"};
         req.hashes                       = {"sha256"};
-        req.message_authentication_codes = {"hkdf-hmac-sha256", "hmac-sha256"};
+        req.message_authentication_codes = {"hkdf-hmac-sha256"};
         req.short_authentication_string  = {mtx::events::msg::SASMethods::Decimal,
                                            mtx::events::msg::SASMethods::Emoji};
 
-        body[this->toClient][this->deviceId.toStdString()] = req;
-        this->canonical_json                               = nlohmann::json(req);
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [body](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to start verification request: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-            });
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                mtx::requests::ToDeviceMessages body;
+                req.transaction_id                                 = this->transaction_id;
+                this->canonical_json                               = nlohmann::json(req);
+                body[this->toClient][this->deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [body](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn(
+                                      "failed to start verification request: {} {}",
+                                      err->matrix_error.error,
+                                      static_cast(err->status_code));
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
 }
 //! sends a verification request
 void
 DeviceVerificationFlow::sendVerificationRequest()
 {
-        QDateTime CurrentTime = QDateTime::currentDateTimeUtc();
-
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationRequest req;
 
-        req.from_device    = http::client()->device_id();
-        req.transaction_id = this->transaction_id;
+        req.from_device = http::client()->device_id();
         req.methods.resize(1);
         req.methods[0] = mtx::events::msg::VerificationMethods::SASv1;
-        req.timestamp  = (uint64_t)CurrentTime.toTime_t();
-
-        body[this->toClient][this->deviceId.toStdString()] = req;
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to send verification request: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-            });
+
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                QDateTime CurrentTime = QDateTime::currentDateTimeUtc();
+
+                req.transaction_id = this->transaction_id;
+                req.timestamp      = (uint64_t)CurrentTime.toTime_t();
+
+                mtx::requests::ToDeviceMessages body;
+
+                body[this->toClient][this->deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn("failed to send verification request: {} {}",
+                                                       err->matrix_error.error,
+                                                       static_cast(err->status_code));
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                std::cout << "lulz" << std::endl;
+        }
 }
 //! cancels a verification flow
 void
 DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code)
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationCancel req;
 
-        req.transaction_id = this->transaction_id;
         if (error_code == DeviceVerificationFlow::Error::UnknownMethod) {
                 req.code   = "m.unknown_method";
                 req.reason = "unknown method recieved";
@@ -457,65 +511,79 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c
                 req.reason = "user cancelled the verification";
         }
 
-        body[this->toClient][deviceId.toStdString()] = req;
-
         emit this->verificationCanceled();
 
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [this](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to cancel verification request: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-                    auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
-                    if (verified_cache.has_value()) {
-                            verified_cache->device_blocked.push_back(this->deviceId.toStdString());
-                            cache::setVerifiedCache(this->userId.toStdString(),
-                                                    verified_cache.value());
-                    } else {
-                            cache::setVerifiedCache(
-                              this->userId.toStdString(),
-                              DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}});
-                    }
-                    this->deleteLater();
-            });
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                req.transaction_id = this->transaction_id;
+                mtx::requests::ToDeviceMessages body;
+
+                body[this->toClient][deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [this](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn(
+                                      "failed to cancel verification request: {} {}",
+                                      err->matrix_error.error,
+                                      static_cast(err->status_code));
+
+                            this->deleteLater();
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
+
+        // TODO : Handle Blocking user better
+        // auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
+        //     if (verified_cache.has_value()) {
+        //             verified_cache->device_blocked.push_back(this->deviceId.toStdString());
+        //             cache::setVerifiedCache(this->userId.toStdString(),
+        //                                     verified_cache.value());
+        //     } else {
+        //             cache::setVerifiedCache(
+        //               this->userId.toStdString(),
+        //               DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}});
+        //     }
 }
 //! sends the verification key
 void
 DeviceVerificationFlow::sendVerificationKey()
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationKey req;
 
-        req.key            = this->sas->public_key();
-        req.transaction_id = this->transaction_id;
-
-        body[this->toClient][deviceId.toStdString()] = req;
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to send verification key: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-            });
+        req.key = this->sas->public_key();
+
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                mtx::requests::ToDeviceMessages body;
+                req.transaction_id = this->transaction_id;
+
+                body[this->toClient][deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn("failed to send verification key: {} {}",
+                                                       err->matrix_error.error,
+                                                       static_cast(err->status_code));
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
 }
 //! sends the mac of the keys
 void
 DeviceVerificationFlow::sendVerificationMac()
 {
-        mtx::requests::ToDeviceMessages body;
         mtx::events::msg::KeyVerificationMac req;
 
         std::string info = "MATRIX_KEY_VERIFICATION_MAC" + http::client()->user_id().to_string() +
                            http::client()->device_id() + this->toClient.to_string() +
                            this->deviceId.toStdString() + this->transaction_id;
 
-        req.transaction_id = this->transaction_id;
         //! this vector stores the type of the key and the key
         std::vector> key_list;
         key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519));
@@ -531,22 +599,28 @@ DeviceVerificationFlow::sendVerificationMac()
         req.keys =
           this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS");
 
-        body[this->toClient][deviceId.toStdString()] = req;
-
-        http::client()
-          ->send_to_device(
-            this->transaction_id, body, [this](mtx::http::RequestErr err) {
-                    if (err)
-                            nhlog::net()->warn("failed to send verification MAC: {} {}",
-                                               err->matrix_error.error,
-                                               static_cast(err->status_code));
-
-                    if (this->isMacVerified == true)
-                            this->acceptDevice();
-                    else
-                            this->isMacVerified = true;
-            });
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+                mtx::requests::ToDeviceMessages body;
+                req.transaction_id                           = this->transaction_id;
+                body[this->toClient][deviceId.toStdString()] = req;
+
+                http::client()
+                  ->send_to_device(
+                    this->transaction_id, body, [this](mtx::http::RequestErr err) {
+                            if (err)
+                                    nhlog::net()->warn("failed to send verification MAC: {} {}",
+                                                       err->matrix_error.error,
+                                                       static_cast(err->status_code));
+
+                            if (this->isMacVerified == true)
+                                    this->acceptDevice();
+                            else
+                                    this->isMacVerified = true;
+                    });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+                req.relates_to = this->relation;
+        }
 }
 //! Completes the verification flow
 void
@@ -555,14 +629,11 @@ DeviceVerificationFlow::acceptDevice()
         auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
         if (verified_cache.has_value()) {
                 verified_cache->device_verified.push_back(this->deviceId.toStdString());
-                for (auto it = verified_cache->device_blocked.begin();
-                     it != verified_cache->device_blocked.end();
-                     it++) {
-                        if (*it == this->deviceId.toStdString()) {
-                                verified_cache->device_blocked.erase(it);
-                        }
-                }
-                cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value());
+                verified_cache->device_blocked.erase(
+                  std::remove(verified_cache->device_blocked.begin(),
+                              verified_cache->device_blocked.end(),
+                              this->deviceId.toStdString()),
+                  verified_cache->device_blocked.end());
         } else {
                 cache::setVerifiedCache(
                   this->userId.toStdString(),
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index edff7c8..3f999e8 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -2,8 +2,8 @@
 
 #include "Olm.h"
 
+#include "MatrixClient.h"
 #include "mtx/responses/crypto.hpp"
-#include 
 #include 
 
 class QTimer;
@@ -19,15 +19,22 @@ class DeviceVerificationFlow : public QObject
         Q_PROPERTY(QString userId READ getUserId WRITE setUserId)
         Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId)
         Q_PROPERTY(Method method READ getMethod WRITE setMethod)
-        Q_PROPERTY(std::vector sasList READ getSasList)
+        Q_PROPERTY(std::vector sasList READ getSasList CONSTANT)
 
 public:
+        enum Type
+        {
+                ToDevice,
+                RoomMsg
+        };
+
         enum Method
         {
                 Decimal,
                 Emoji
         };
         Q_ENUM(Method)
+
         enum Error
         {
                 UnknownMethod,
@@ -39,7 +46,9 @@ public:
         };
         Q_ENUM(Error)
 
-        DeviceVerificationFlow(QObject *parent = nullptr);
+        DeviceVerificationFlow(
+          QObject *parent              = nullptr,
+          DeviceVerificationFlow::Type = DeviceVerificationFlow::Type::ToDevice);
         QString getTransactionId();
         QString getUserId();
         QString getDeviceId();
@@ -90,6 +99,7 @@ private:
         QString userId;
         QString deviceId;
         Method method;
+        Type type;
         bool sender;
 
         QTimer *timeout = nullptr;
@@ -101,4 +111,5 @@ private:
         mtx::identifiers::User toClient;
         std::vector sasList;
         std::map device_keys;
+        mtx::common::ReplyRelatesTo relation;
 };
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index 0618206..869687f 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -72,8 +72,15 @@ struct EventBody
         template
         std::string operator()(const mtx::events::Event &e)
         {
-                if constexpr (is_detected::value)
-                        return e.content.body;
+                if constexpr (is_detected::value) {
+                        if constexpr (std::is_same_v,
+                                                     std::remove_cv_t>)
+                                return e.content.body ? e.content.body.value() : "";
+                        else if constexpr (std::is_same_v<
+                                             std::string,
+                                             std::remove_cv_t>)
+                                return e.content.body;
+                }
                 return "";
         }
 };
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 7d7037c..ff6ea2f 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -5,10 +5,10 @@
 
 #include "Cache.h"
 #include "ChatPage.h"
+#include "DeviceVerificationFlow.h"
 #include "Logging.h"
 #include "MatrixClient.h"
 #include "Utils.h"
-#include 
 
 static const std::string STORAGE_SECRET_KEY("secret");
 constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
@@ -77,21 +77,42 @@ handle_to_device_messages(const std::vectorrecievedDeviceVerificationAccept(msg);
+                        auto message = std::get<
+                          mtx::events::DeviceEvent>(msg);
+                        ChatPage::instance()->recievedDeviceVerificationAccept(message.content);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) {
-                        ChatPage::instance()->recievedDeviceVerificationRequest(msg);
+                        auto message = std::get<
+                          mtx::events::DeviceEvent>(msg);
+                        ChatPage::instance()->recievedDeviceVerificationRequest(message.content,
+                                                                                message.sender);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) {
-                        ChatPage::instance()->recievedDeviceVerificationCancel(msg);
+                        auto message = std::get<
+                          mtx::events::DeviceEvent>(msg);
+                        ChatPage::instance()->recievedDeviceVerificationCancel(message.content);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) {
-                        ChatPage::instance()->recievedDeviceVerificationKey(msg);
+                        auto message =
+                          std::get>(
+                            msg);
+                        ChatPage::instance()->recievedDeviceVerificationKey(message.content);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) {
-                        ChatPage::instance()->recievedDeviceVerificationMac(msg);
+                        auto message =
+                          std::get>(
+                            msg);
+                        ChatPage::instance()->recievedDeviceVerificationMac(message.content);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) {
-                        ChatPage::instance()->recievedDeviceVerificationStart(msg);
+                        auto message = std::get<
+                          mtx::events::DeviceEvent>(msg);
+                        ChatPage::instance()->recievedDeviceVerificationStart(message.content,
+                                                                              message.sender);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) {
-                        ChatPage::instance()->recievedDeviceVerificationReady(msg);
+                        auto message = std::get<
+                          mtx::events::DeviceEvent>(msg);
+                        ChatPage::instance()->recievedDeviceVerificationReady(message.content);
                 } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) {
-                        ChatPage::instance()->recievedDeviceVerificationDone(msg);
+                        auto message =
+                          std::get>(
+                            msg);
+                        ChatPage::instance()->recievedDeviceVerificationDone(message.content);
                 } else {
                         nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2));
                 }
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 773a5a2..71cc53c 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -22,6 +22,8 @@
 #include "Utils.h"
 #include "dialogs/RawMessage.h"
 
+#include 
+
 Q_DECLARE_METATYPE(QModelIndex)
 
 namespace std {
@@ -116,7 +118,41 @@ struct RoomEventType
         {
                 return qml_mtx_events::EventType::VideoMessage;
         }
-
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationRequest;
+        }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationStart;
+        }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationMac;
+        }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationAccept;
+        }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationCancel;
+        }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationKey;
+        }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationDone;
+        }
         qml_mtx_events::EventType operator()(const mtx::events::Event &)
         {
                 return qml_mtx_events::EventType::Redacted;
@@ -572,6 +608,155 @@ TimelineModel::updateLastMessage()
         }
 }
 
+std::vector
+TimelineModel::internalAddEvents(
+  const std::vector &timeline)
+{
+        std::vector ids;
+        for (auto e : timeline) {
+                QString id = QString::fromStdString(mtx::accessors::event_id(e));
+
+                if (this->events.contains(id)) {
+                        this->events.insert(id, e);
+                        int idx = idToIndex(id);
+                        emit dataChanged(index(idx, 0), index(idx, 0));
+                        continue;
+                }
+
+                QString txid = QString::fromStdString(mtx::accessors::transaction_id(e));
+                if (this->pending.removeOne(txid)) {
+                        this->events.insert(id, e);
+                        this->events.remove(txid);
+                        int idx = idToIndex(txid);
+                        if (idx < 0) {
+                                nhlog::ui()->warn("Received index out of range");
+                                continue;
+                        }
+                        eventOrder[idx] = id;
+                        emit dataChanged(index(idx, 0), index(idx, 0));
+                        continue;
+                }
+
+                if (std::get_if>(
+                      &e)) {
+                        std::cout << "got a request" << std::endl;
+                }
+
+                if (auto cancelVerification =
+                      std::get_if>(
+                        &e)) {
+                        std::cout<<"it is happening"<content.relates_to.has_value()) {
+                                QString event_id = QString::fromStdString(
+                                  cancelVerification->content.relates_to.value()
+                                    .in_reply_to.event_id);
+                                auto request =
+                                  std::find(eventOrder.begin(), eventOrder.end(), event_id);
+                                if (request != eventOrder.end()) {
+                                        auto event = events.value(event_id);
+                                        auto e     = std::get_if>(&event);
+                                        std::cout<>(&e)) {
+                        QString redacts = QString::fromStdString(redaction->redacts);
+                        auto redacted   = std::find(eventOrder.begin(), eventOrder.end(), redacts);
+
+                        auto event = events.value(redacts);
+                        if (auto reaction =
+                              std::get_if>(
+                                &event)) {
+                                QString reactedTo =
+                                  QString::fromStdString(reaction->content.relates_to.event_id);
+                                reactions[reactedTo].removeReaction(*reaction);
+                                int idx = idToIndex(reactedTo);
+                                if (idx >= 0)
+                                        emit dataChanged(index(idx, 0), index(idx, 0));
+                        }
+
+                        if (redacted != eventOrder.end()) {
+                                auto redactedEvent = std::visit(
+                                  [](const auto &ev)
+                                    -> mtx::events::RoomEvent {
+                                          mtx::events::RoomEvent
+                                            replacement                = {};
+                                          replacement.event_id         = ev.event_id;
+                                          replacement.room_id          = ev.room_id;
+                                          replacement.sender           = ev.sender;
+                                          replacement.origin_server_ts = ev.origin_server_ts;
+                                          replacement.type             = ev.type;
+                                          return replacement;
+                                  },
+                                  e);
+                                events.insert(redacts, redactedEvent);
+
+                                int row = (int)std::distance(eventOrder.begin(), redacted);
+                                emit dataChanged(index(row, 0), index(row, 0));
+                        }
+
+                        continue; // don't insert redaction into timeline
+                }
+
+                if (auto reaction =
+                      std::get_if>(&e)) {
+                        QString reactedTo =
+                          QString::fromStdString(reaction->content.relates_to.event_id);
+                        events.insert(id, e);
+
+                        // remove local echo
+                        if (!txid.isEmpty()) {
+                                auto rCopy     = *reaction;
+                                rCopy.event_id = txid.toStdString();
+                                reactions[reactedTo].removeReaction(rCopy);
+                        }
+
+                        reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction);
+                        int idx = idToIndex(reactedTo);
+                        if (idx >= 0)
+                                emit dataChanged(index(idx, 0), index(idx, 0));
+                        continue; // don't insert reaction into timeline
+                }
+
+                if (auto event =
+                      std::get_if>(&e)) {
+                        auto e_      = decryptEvent(*event).event;
+                        auto encInfo = mtx::accessors::file(e_);
+
+                        if (encInfo)
+                                emit newEncryptedImage(encInfo.value());
+                }
+
+                this->events.insert(id, e);
+                ids.push_back(id);
+
+                auto replyTo  = mtx::accessors::in_reply_to_event(e);
+                auto qReplyTo = QString::fromStdString(replyTo);
+                if (!replyTo.empty() && !events.contains(qReplyTo)) {
+                        http::client()->get_event(
+                          this->room_id_.toStdString(),
+                          replyTo,
+                          [this, id, replyTo](
+                            const mtx::events::collections::TimelineEvents &timeline,
+                            mtx::http::RequestErr err) {
+                                  if (err) {
+                                          nhlog::net()->error(
+                                            "Failed to retrieve event with id {}, which was "
+                                            "requested to show the replyTo for event {}",
+                                            replyTo,
+                                            id.toStdString());
+                                          return;
+                                  }
+                                  emit eventFetched(id, timeline);
+                          });
+                }
+        }
+        return ids;
+}
+
 void
 TimelineModel::setCurrentIndex(int index)
 {
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 104a475..708ed38 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -84,6 +84,14 @@ enum EventType
         VideoMessage,
         Redacted,
         UnknownMessage,
+        KeyVerificationRequest,
+        KeyVerificationStart,
+        KeyVerificationMac,
+        KeyVerificationAccept,
+        KeyVerificationCancel,
+        KeyVerificationKey,
+        KeyVerificationDone,
+        KeyVerificationReady
 };
 Q_ENUM_NS(EventType)
 
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 81c8d6d..02b74d2 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -101,7 +101,15 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
   , blurhashProvider(new BlurhashProvider())
   , settings(userSettings)
 {
-        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+
         qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
                                          "im.nheko",
                                          1,
@@ -181,21 +189,19 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           dynamic_cast(parent),
           &ChatPage::recievedDeviceVerificationRequest,
           this,
-          [this](const mtx::events::collections::DeviceEvents &message) {
-                  auto msg =
-                    std::get>(message);
+          [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) {
                   auto flow = new DeviceVerificationFlow(this);
-                  if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) {
-                          if (std::find(msg.content.methods.begin(),
-                                        msg.content.methods.end(),
+                  if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) {
+                          if (std::find(msg.methods.begin(),
+                                        msg.methods.end(),
                                         mtx::events::msg::VerificationMethods::SASv1) !=
-                              msg.content.methods.end()) {
+                              msg.methods.end()) {
                                   //   flow->sendVerificationReady();
                                   emit newDeviceVerificationRequest(
                                     std::move(flow),
-                                    QString::fromStdString(msg.content.transaction_id),
-                                    QString::fromStdString(msg.sender),
-                                    QString::fromStdString(msg.content.from_device));
+                                    QString::fromStdString(msg.transaction_id.value()),
+                                    QString::fromStdString(sender),
+                                    QString::fromStdString(msg.from_device));
                           } else {
                                   flow->cancelVerification(
                                     DeviceVerificationFlow::Error::UnknownMethod);
@@ -206,33 +212,29 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           dynamic_cast(parent),
           &ChatPage::recievedDeviceVerificationStart,
           this,
-          [this](const mtx::events::collections::DeviceEvents &message) {
-                  auto msg =
-                    std::get>(message);
+          [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) {
                   auto flow            = new DeviceVerificationFlow(this);
-                  flow->canonical_json = nlohmann::json(msg.content);
-                  if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) {
-                          if ((std::find(msg.content.key_agreement_protocols.begin(),
-                                         msg.content.key_agreement_protocols.end(),
+                  flow->canonical_json = nlohmann::json(msg);
+                  if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) {
+                          if ((std::find(msg.key_agreement_protocols.begin(),
+                                         msg.key_agreement_protocols.end(),
                                          "curve25519-hkdf-sha256") !=
-                               msg.content.key_agreement_protocols.end()) &&
-                              (std::find(msg.content.hashes.begin(),
-                                         msg.content.hashes.end(),
-                                         "sha256") != msg.content.hashes.end()) &&
-                              (std::find(msg.content.message_authentication_codes.begin(),
-                                         msg.content.message_authentication_codes.end(),
+                               msg.key_agreement_protocols.end()) &&
+                              (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") !=
+                               msg.hashes.end()) &&
+                              (std::find(msg.message_authentication_codes.begin(),
+                                         msg.message_authentication_codes.end(),
                                          "hmac-sha256") !=
-                               msg.content.message_authentication_codes.end())) {
-                                  if (std::find(msg.content.short_authentication_string.begin(),
-                                                msg.content.short_authentication_string.end(),
+                               msg.message_authentication_codes.end())) {
+                                  if (std::find(msg.short_authentication_string.begin(),
+                                                msg.short_authentication_string.end(),
                                                 mtx::events::msg::SASMethods::Emoji) !=
-                                      msg.content.short_authentication_string.end()) {
+                                      msg.short_authentication_string.end()) {
                                           flow->setMethod(DeviceVerificationFlow::Method::Emoji);
-                                  } else if (std::find(
-                                               msg.content.short_authentication_string.begin(),
-                                               msg.content.short_authentication_string.end(),
-                                               mtx::events::msg::SASMethods::Decimal) !=
-                                             msg.content.short_authentication_string.end()) {
+                                  } else if (std::find(msg.short_authentication_string.begin(),
+                                                       msg.short_authentication_string.end(),
+                                                       mtx::events::msg::SASMethods::Decimal) !=
+                                             msg.short_authentication_string.end()) {
                                           flow->setMethod(DeviceVerificationFlow::Method::Decimal);
                                   } else {
                                           flow->cancelVerification(
@@ -241,10 +243,10 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
                                   }
                                   emit newDeviceVerificationRequest(
                                     std::move(flow),
-                                    QString::fromStdString(msg.content.transaction_id),
-                                    QString::fromStdString(msg.sender),
-                                    QString::fromStdString(msg.content.from_device));
-                                  flow->canonical_json = nlohmann::json(msg.content);
+                                    QString::fromStdString(msg.transaction_id.value()),
+                                    QString::fromStdString(sender),
+                                    QString::fromStdString(msg.from_device));
+                                  flow->canonical_json = nlohmann::json(msg);
                           } else {
                                   flow->cancelVerification(
                                     DeviceVerificationFlow::Error::UnknownMethod);
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index a438ef5..71aee5e 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -133,4 +133,11 @@ private:
 
         DeviceVerificationList *dvList;
 };
-Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationDone)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationKey)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationMac)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationReady)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationRequest)
+Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationStart)
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 6ae04d0..3499384 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -6,6 +6,8 @@
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
 
+#include  // only for debugging
+
 UserProfile::UserProfile(QString roomid, QString userid, QObject *parent)
   : QObject(parent)
   , roomid_(roomid)
@@ -74,6 +76,12 @@ UserProfile::avatarUrl()
         return cache::avatarUrl(roomid_, userid_);
 }
 
+bool
+UserProfile::getUserStatus()
+{
+        return isUserVerified;
+}
+
 void
 UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
@@ -100,6 +108,7 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
 
                 // TODO: Verify signatures and ignore those that don't pass.
                 verification::Status verified = verification::Status::UNVERIFIED;
+                isUserVerified                = device_verified->is_user_verified;
                 if (device_verified.has_value()) {
                         if (std::find(device_verified->cross_verified.begin(),
                                       device_verified->cross_verified.end(),
@@ -174,4 +183,29 @@ UserProfile::startChat()
         if (utils::localUser() != this->userid_)
                 req.invite = {this->userid_.toStdString()};
         emit ChatPage::instance()->createRoom(req);
+}
+
+void
+UserProfile::verifyUser()
+{
+        std::cout << "Checking if to start to device verification or room message verification"
+                  << std::endl;
+        auto joined_rooms = cache::joinedRooms();
+        auto room_infos   = cache::getRoomInfo(joined_rooms);
+
+        for (std::string room_id : joined_rooms) {
+                if ((room_infos[QString::fromStdString(room_id)].member_count == 2) &&
+                    cache::isRoomEncrypted(room_id)) {
+                        auto room_members = cache::roomMembers(room_id);
+                        if (std::find(room_members.begin(),
+                                      room_members.end(),
+                                      (this->userid()).toStdString()) != room_members.end()) {
+                                std::cout << "FOUND A ENCRYPTED ROOM WITH THIS USER : " << room_id
+                                          << std::endl;
+                                return;
+                        }
+                }
+        }
+
+        std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl;
 }
\ No newline at end of file
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 4e04840..3f9cbe6 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -81,6 +81,7 @@ class UserProfile : public QObject
         Q_PROPERTY(QString userid READ userid CONSTANT)
         Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
         Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
+        Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT)
 public:
         UserProfile(QString roomid, QString userid, QObject *parent = 0);
 
@@ -89,17 +90,20 @@ public:
         QString userid();
         QString displayName();
         QString avatarUrl();
+        bool getUserStatus();
 
         Q_INVOKABLE void fetchDeviceList(const QString &userID);
         Q_INVOKABLE void banUser();
         // Q_INVOKABLE void ignoreUser();
         Q_INVOKABLE void kickUser();
         Q_INVOKABLE void startChat();
+        Q_INVOKABLE void verifyUser();
 
 private:
         QString roomid_, userid_;
         std::optional cross_verified;
         DeviceInfoModel deviceList_;
+        bool isUserVerified = false;
 
         void callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,

From 3635c185e93a6f7bf6e2f9c11b27966c2ee665ea Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Wed, 29 Jul 2020 03:25:47 +0530
Subject: [PATCH 29/70] Add Room Verification Messages

---
 resources/qml/TimelineView.qml                |  15 +-
 .../DeviceVerification.qml                    |   7 +-
 src/ChatPage.h                                |   4 +
 src/DeviceVerificationFlow.cpp                |  56 +++++--
 src/DeviceVerificationFlow.h                  |  22 ++-
 src/timeline/TimelineModel.cpp                | 145 +++++++++++++++---
 src/timeline/TimelineModel.h                  |   5 +
 src/timeline/TimelineViewManager.cpp          |  37 ++++-
 src/timeline/TimelineViewManager.h            |   3 +-
 9 files changed, 245 insertions(+), 49 deletions(-)

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 699efc5..c6fc385 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -106,13 +106,20 @@ Page {
 		}
 		Connections {
 			target: TimelineManager
-			function onNewDeviceVerificationRequest(flow,transactionId,userId,deviceId) {
+			function onNewDeviceVerificationRequest(flow,transactionId,userId,deviceId,isRequest) {
 				flow.userId = userId;
 				flow.sender = false;
 				flow.deviceId = deviceId;
-				flow.tranId = transactionId;
-				deviceVerificationList.add(flow.tranId);
-				var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow});
+				switch(flow.type){
+					case DeviceVerificationFlow.ToDevice:
+					    flow.tranId = transactionId;
+						deviceVerificationList.add(flow.tranId);
+						break;
+					case DeviceVerificationFlow.RoomMsg:
+						deviceVerificationList.add(flow.tranId);
+						break;
+				}
+				var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow,isRequest = isRequest});
 				dialog.show();
 			}
 		}
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 4d734a6..8e74d1c 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -23,6 +23,8 @@ ApplicationWindow {
 	}
 
 	property var flow
+	property bool isRequest
+
 	Connections {
 		target: flow
 		onVerificationCanceled: stack.replace(partnerAborted)
@@ -155,7 +157,10 @@ ApplicationWindow {
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
                         }
-						onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.acceptVerificationRequest(); }
+						onClicked: { 
+							stack.replace(awaitingVerificationRequestAccept); 
+							isRequest?flow.sendVerificationReady():flow.acceptVerificationRequest(); 
+						}
 					}
 				}
 			}
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 0e7c889..72adfe1 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -52,6 +52,7 @@ class TopRoomBar;
 class UserInfoWidget;
 class UserSettings;
 class NotificationsManager;
+class TimelineModel;
 
 constexpr int CONSENSUS_TIMEOUT      = 1000;
 constexpr int SHOW_CONTENT_TIMEOUT   = 3000;
@@ -171,6 +172,9 @@ signals:
         void recievedDeviceVerificationRequest(
           const mtx::events::msg::KeyVerificationRequest &message,
           std::string sender);
+        void recievedRoomDeviceVerificationRequest(
+          const mtx::events::RoomEvent &message,
+          TimelineModel *model);
         void recievedDeviceVerificationCancel(
           const mtx::events::msg::KeyVerificationCancel &message);
         void recievedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 69de493..0f521f9 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -1,7 +1,9 @@
 #include "DeviceVerificationFlow.h"
+
 #include "Cache.h"
 #include "ChatPage.h"
 #include "Logging.h"
+#include "timeline/TimelineModel.h"
 
 #include 
 #include 
@@ -12,12 +14,14 @@ static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
 
 namespace msgs = mtx::events::msg;
 
-DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type)
+DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type)
+  : type(flow_type)
 {
         timeout = new QTimer(this);
         timeout->setSingleShot(true);
         this->sas           = olm::client()->sas_init();
         this->isMacVerified = false;
+
         connect(timeout, &QTimer::timeout, this, [this]() {
                 emit timedout();
                 this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
@@ -267,6 +271,12 @@ DeviceVerificationFlow::getMethod()
         return this->method;
 }
 
+DeviceVerificationFlow::Type
+DeviceVerificationFlow::getType()
+{
+        return this->type;
+}
+
 bool
 DeviceVerificationFlow::getSender()
 {
@@ -279,6 +289,12 @@ DeviceVerificationFlow::getSasList()
         return this->sasList;
 }
 
+void
+DeviceVerificationFlow::setModel(TimelineModel *&model)
+{
+        this->model_ = model;
+}
+
 void
 DeviceVerificationFlow::setTransactionId(QString transaction_id_)
 {
@@ -318,6 +334,12 @@ DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_)
         this->method = method_;
 }
 
+void
+DeviceVerificationFlow::setType(Type type)
+{
+        this->type = type;
+}
+
 void
 DeviceVerificationFlow::setSender(bool sender_)
 {
@@ -328,6 +350,13 @@ DeviceVerificationFlow::setSender(bool sender_)
                 this->relation.in_reply_to.event_id = http::client()->generate_txn_id();
 }
 
+void
+DeviceVerificationFlow::setEventId(std::string event_id)
+{
+        this->relation.in_reply_to.event_id = event_id;
+        this->transaction_id                = event_id;
+}
+
 //! accepts a verification
 void
 DeviceVerificationFlow::acceptVerificationRequest()
@@ -361,8 +390,9 @@ DeviceVerificationFlow::acceptVerificationRequest()
                                       err->matrix_error.error,
                                       static_cast(err->status_code));
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 }
 //! responds verification request
@@ -389,8 +419,9 @@ DeviceVerificationFlow::sendVerificationReady()
                                                        err->matrix_error.error,
                                                        static_cast(err->status_code));
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 }
 //! accepts a verification
@@ -414,8 +445,9 @@ DeviceVerificationFlow::sendVerificationDone()
                                                        err->matrix_error.error,
                                                        static_cast(err->status_code));
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 }
 //! starts the verification flow
@@ -448,8 +480,9 @@ DeviceVerificationFlow::startVerificationRequest()
                                       err->matrix_error.error,
                                       static_cast(err->status_code));
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 }
 //! sends a verification request
@@ -481,8 +514,8 @@ DeviceVerificationFlow::sendVerificationRequest()
                                                        err->matrix_error.error,
                                                        static_cast(err->status_code));
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
-                std::cout << "lulz" << std::endl;
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                (model_.value())->sendMessage(req);
         }
 }
 //! cancels a verification flow
@@ -531,8 +564,9 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c
 
                             this->deleteLater();
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 
         // TODO : Handle Blocking user better
@@ -570,8 +604,9 @@ DeviceVerificationFlow::sendVerificationKey()
                                                        err->matrix_error.error,
                                                        static_cast(err->status_code));
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 }
 //! sends the mac of the keys
@@ -618,8 +653,9 @@ DeviceVerificationFlow::sendVerificationMac()
                             else
                                     this->isMacVerified = true;
                     });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
                 req.relates_to = this->relation;
+                (model_.value())->sendMessage(req);
         }
 }
 //! Completes the verification flow
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 3f999e8..bec9f1e 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -10,6 +10,8 @@ class QTimer;
 
 using sas_ptr = std::unique_ptr;
 
+struct TimelineModel;
+
 class DeviceVerificationFlow : public QObject
 {
         Q_OBJECT
@@ -19,6 +21,7 @@ class DeviceVerificationFlow : public QObject
         Q_PROPERTY(QString userId READ getUserId WRITE setUserId)
         Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId)
         Q_PROPERTY(Method method READ getMethod WRITE setMethod)
+        Q_PROPERTY(Type type READ getType WRITE setType)
         Q_PROPERTY(std::vector sasList READ getSasList CONSTANT)
 
 public:
@@ -27,6 +30,7 @@ public:
                 ToDevice,
                 RoomMsg
         };
+        Q_ENUM(Type)
 
         enum Method
         {
@@ -49,17 +53,24 @@ public:
         DeviceVerificationFlow(
           QObject *parent              = nullptr,
           DeviceVerificationFlow::Type = DeviceVerificationFlow::Type::ToDevice);
+        // getters
         QString getTransactionId();
         QString getUserId();
         QString getDeviceId();
         Method getMethod();
+        Type getType();
         std::vector getSasList();
-        void setTransactionId(QString transaction_id_);
         bool getSender();
+        // setters
+        void setModel(TimelineModel *&model);
+        void setTransactionId(QString transaction_id_);
         void setUserId(QString userID);
         void setDeviceId(QString deviceID);
         void setMethod(Method method_);
+        void setType(Type type_);
         void setSender(bool sender_);
+        void setEventId(std::string event_id);
+
         void callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,
                          std::string user_id);
@@ -96,20 +107,25 @@ signals:
         void refreshProfile();
 
 private:
+        // general
         QString userId;
         QString deviceId;
         Method method;
         Type type;
         bool sender;
-
         QTimer *timeout = nullptr;
         sas_ptr sas;
         bool isMacVerified = false;
         std::string mac_method;
-        std::string transaction_id;
         std::string commitment;
         mtx::identifiers::User toClient;
         std::vector sasList;
         std::map device_keys;
+        // for to_device messages
+        std::string transaction_id;
+        // for room messages
+        std::optional room_id;
+        std::optional event_id;
+        std::optional model_;
         mtx::common::ReplyRelatesTo relation;
 };
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 71cc53c..adf207a 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -138,6 +138,11 @@ struct RoomEventType
         {
                 return qml_mtx_events::EventType::KeyVerificationAccept;
         }
+        qml_mtx_events::EventType operator()(
+          const mtx::events::Event &)
+        {
+                return qml_mtx_events::EventType::KeyVerificationReady;
+        }
         qml_mtx_events::EventType operator()(
           const mtx::events::Event &)
         {
@@ -637,30 +642,6 @@ TimelineModel::internalAddEvents(
                         continue;
                 }
 
-                if (std::get_if>(
-                      &e)) {
-                        std::cout << "got a request" << std::endl;
-                }
-
-                if (auto cancelVerification =
-                      std::get_if>(
-                        &e)) {
-                        std::cout<<"it is happening"<content.relates_to.has_value()) {
-                                QString event_id = QString::fromStdString(
-                                  cancelVerification->content.relates_to.value()
-                                    .in_reply_to.event_id);
-                                auto request =
-                                  std::find(eventOrder.begin(), eventOrder.end(), event_id);
-                                if (request != eventOrder.end()) {
-                                        auto event = events.value(event_id);
-                                        auto e     = std::get_if>(&event);
-                                        std::cout<>(&e)) {
                         QString redacts = QString::fromStdString(redaction->redacts);
@@ -728,6 +709,55 @@ TimelineModel::internalAddEvents(
 
                         if (encInfo)
                                 emit newEncryptedImage(encInfo.value());
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(
+                              &e_)) {
+                                last_verification_request_event = *msg;
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(
+                              &e_)) {
+                                last_verification_cancel_event = *msg;
+                                ChatPage::instance()->recievedDeviceVerificationCancel(
+                                  msg->content);
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(
+                              &e_)) {
+                                ChatPage::instance()->recievedDeviceVerificationAccept(
+                                  msg->content);
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(&e_)) {
+                                ChatPage::instance()->recievedDeviceVerificationKey(msg->content);
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(&e_)) {
+                                ChatPage::instance()->recievedDeviceVerificationMac(msg->content);
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(
+                              &e_)) {
+                                ChatPage::instance()->recievedDeviceVerificationReady(msg->content);
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(&e_)) {
+                                ChatPage::instance()->recievedDeviceVerificationDone(msg->content);
+                        }
+
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(
+                              &e_)) {
+                                ChatPage::instance()->recievedDeviceVerificationStart(msg->content,
+                                                                                      msg->sender);
+                        }
                 }
 
                 this->events.insert(id, e);
@@ -754,6 +784,13 @@ TimelineModel::internalAddEvents(
                           });
                 }
         }
+
+        if (last_verification_request_event.origin_server_ts >
+            last_verification_cancel_event.origin_server_ts) {
+                ChatPage::instance()->recievedRoomDeviceVerificationRequest(
+                  last_verification_request_event, this);
+        }
+
         return ids;
 }
 
@@ -1263,6 +1300,20 @@ struct SendMessageVisitor
         TimelineModel *model_;
 };
 
+void
+TimelineModel::processOnePendingMessage()
+{
+        if (pending.isEmpty())
+                return;
+
+        QString txn_id_qstr = pending.first();
+
+        auto event = events.value(txn_id_qstr);
+        std::cout << "Inside the process one pending message" << std::endl;
+        std::cout << std::visit([](auto &e) { return json(e); }, event).dump(2) << std::endl;
+        std::visit(SendMessageVisitor{txn_id_qstr, this}, event);
+}
+
 void
 TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
 {
@@ -1275,7 +1326,51 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
           },
           event);
 
-        std::visit(SendMessageVisitor{this}, event);
+        if (std::get_if>(&event)) {
+                std::visit(
+                  [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationReady; },
+                  event);
+        }
+        if (std::get_if>(&event)) {
+                std::visit(
+                  [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationStart; },
+                  event);
+        }
+        if (std::get_if>(&event)) {
+                std::visit([](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationKey; },
+                           event);
+        }
+        if (std::get_if>(&event)) {
+                std::visit([](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationMac; },
+                           event);
+        }
+        if (std::get_if>(&event)) {
+                std::visit(
+                  [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationDone; }, event);
+        }
+        if (std::get_if>(&event)) {
+                std::visit(
+                  [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationCancel; },
+                  event);
+        }
+        if (std::get_if>(&event)) {
+                std::visit(
+                  [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationAccept; },
+                  event);
+        }
+
+        internalAddEvents({event});
+
+        QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event));
+        pending.push_back(txn_id_qstr);
+        if (!std::get_if>(&event)) {
+                beginInsertRows(QModelIndex(), 0, 0);
+                this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr);
+                endInsertRows();
+        }
+        updateLastMessage();
+
+        emit nextPendingMessage();
 }
 
 bool
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 708ed38..1b6f999 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -297,6 +297,11 @@ private:
         std::vector typingUsers_;
 
         TimelineViewManager *manager_;
+        // probably not the best way to do
+        mtx::events::RoomEvent
+          last_verification_request_event;
+        mtx::events::RoomEvent
+          last_verification_cancel_event;
 
         friend struct SendMessageVisitor;
 };
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 02b74d2..c16e09d 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -17,6 +17,8 @@
 #include "emoji/EmojiModel.h"
 #include "emoji/Provider.h"
 
+#include  //only for debugging
+
 Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
 Q_DECLARE_METATYPE(std::vector)
 
@@ -185,18 +187,44 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
                 &ChatPage::decryptSidebarChanged,
                 this,
                 &TimelineViewManager::updateEncryptedDescriptions);
+        connect(
+          dynamic_cast(parent),
+          &ChatPage::recievedRoomDeviceVerificationRequest,
+          this,
+          [this](const mtx::events::RoomEvent &message,
+                 TimelineModel *model) {
+                  if (!(this->dvList->exist(QString::fromStdString(message.event_id)))) {
+                          auto flow =
+                            new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::RoomMsg);
+                          if (std::find(message.content.methods.begin(),
+                                        message.content.methods.end(),
+                                        mtx::events::msg::VerificationMethods::SASv1) !=
+                              message.content.methods.end()) {
+                                  flow->setModel(model);
+                                  flow->setEventId(message.event_id);
+                                  emit newDeviceVerificationRequest(
+                                    std::move(flow),
+                                    QString::fromStdString(message.event_id),
+                                    QString::fromStdString(message.sender),
+                                    QString::fromStdString(message.content.from_device),
+                                    true);
+                          } else {
+                                  flow->cancelVerification(
+                                    DeviceVerificationFlow::Error::UnknownMethod);
+                          }
+                  }
+          });
         connect(
           dynamic_cast(parent),
           &ChatPage::recievedDeviceVerificationRequest,
           this,
           [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) {
-                  auto flow = new DeviceVerificationFlow(this);
                   if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) {
+                          auto flow = new DeviceVerificationFlow(this);
                           if (std::find(msg.methods.begin(),
                                         msg.methods.end(),
                                         mtx::events::msg::VerificationMethods::SASv1) !=
                               msg.methods.end()) {
-                                  //   flow->sendVerificationReady();
                                   emit newDeviceVerificationRequest(
                                     std::move(flow),
                                     QString::fromStdString(msg.transaction_id.value()),
@@ -213,9 +241,9 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           &ChatPage::recievedDeviceVerificationStart,
           this,
           [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) {
-                  auto flow            = new DeviceVerificationFlow(this);
-                  flow->canonical_json = nlohmann::json(msg);
                   if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) {
+                          auto flow            = new DeviceVerificationFlow(this);
+                          flow->canonical_json = nlohmann::json(msg);
                           if ((std::find(msg.key_agreement_protocols.begin(),
                                          msg.key_agreement_protocols.end(),
                                          "curve25519-hkdf-sha256") !=
@@ -246,7 +274,6 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
                                     QString::fromStdString(msg.transaction_id.value()),
                                     QString::fromStdString(sender),
                                     QString::fromStdString(msg.from_device));
-                                  flow->canonical_json = nlohmann::json(msg);
                           } else {
                                   flow->cancelVerification(
                                     DeviceVerificationFlow::Error::UnknownMethod);
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 71aee5e..031d07c 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -72,7 +72,8 @@ signals:
         void newDeviceVerificationRequest(DeviceVerificationFlow *flow,
                                           QString transactionId,
                                           QString userId,
-                                          QString deviceId);
+                                          QString deviceId,
+                                          bool isRequest = false);
 
 public slots:
         void updateReadReceipts(const QString &room_id, const std::vector &event_ids);

From 2e20049b3695d0aa7ca09db079bcc39c0485d098 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sun, 9 Aug 2020 08:35:15 +0530
Subject: [PATCH 30/70] [WIP] Room-Verification Messages

---
 resources/qml/Reactions.qml                   |   7 +-
 resources/qml/TimelineRow.qml                 |   2 +-
 resources/qml/TimelineView.qml                |   5 -
 resources/qml/UserProfile.qml                 |  15 +-
 .../DeviceVerification.qml                    |   4 +-
 src/ChatPage.cpp                              |  11 +-
 src/DeviceVerificationFlow.cpp                | 197 +++++------
 src/DeviceVerificationFlow.h                  |   2 +-
 src/EventAccessors.cpp                        |  11 +-
 src/timeline/EventStore.cpp                   | 221 +++++++-----
 src/timeline/EventStore.h                     |   8 +
 src/timeline/TimelineModel.cpp                | 333 +++++-------------
 src/timeline/TimelineModel.h                  |  16 +-
 src/ui/UserProfile.cpp                        |  56 ++-
 src/ui/UserProfile.h                          |   6 +-
 15 files changed, 401 insertions(+), 493 deletions(-)

diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml
index 11109d7..9fc30f6 100644
--- a/resources/qml/Reactions.qml
+++ b/resources/qml/Reactions.qml
@@ -35,13 +35,8 @@ Flow {
 			ToolTip.text: modelData.users
 
 			onClicked: {
-<<<<<<< HEAD
 				console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + modelData.selfReactedEvent)
-				timelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key)
-=======
-				console.debug("Picked " + model.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + model.selfReactedEvent)
-				TimelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, model.key, model.selfReactedEvent)
->>>>>>> Fix presence indicator
+				TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key)
 			}
 
 
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index db58eb2..b464b76 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -48,7 +48,7 @@ Item {
 			// fancy reply, if this is a reply
 			Reply {
 				visible: model.replyTo
-				modelData: chat.model.getDump(model.replyTo)
+				modelData: chat.model.getDump(model.replyTo,model.id)
 				userColor: TimelineManager.userColor(modelData.userId, colors.window)
 			}
 
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index c6fc385..86b78a1 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -388,13 +388,8 @@ Page {
 						anchors.rightMargin: 20
 						anchors.bottom: parent.bottom
 
-<<<<<<< HEAD
 						modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {}
-						userColor: timelineManager.userColor(modelData.userId, colors.window)
-=======
-						modelData: chat.model ? chat.model.getDump(chat.model.reply) : {}
 						userColor: TimelineManager.userColor(modelData.userId, colors.window)
->>>>>>> Fix presence indicator
 					}
 
 					ImageButton {
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index c7dbc9a..9b53ff3 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -90,7 +90,12 @@ ApplicationWindow{
 					verticalAlignment: Text.AlignVCenter
 				}
 				onClicked: {
-					profile.verifyUser();
+					var newFlow = profile.createFlow(true);
+					newFlow.userId = profile.userid;
+					newFlow.sender = true;
+					deviceVerificationList.add(newFlow.tranId);
+					var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest: true});
+					dialog.show();
 				}
 			}
 
@@ -192,14 +197,16 @@ ApplicationWindow{
 								id: verifyButton
 								text:(model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify"
 								onClicked: {
-									var newFlow = deviceVerificationFlow.createObject(userProfileDialog,
-									{userId : profile.userid, sender: true, deviceId : model.deviceId});
+									var newFlow = profile.createFlow(false);
+									newFlow.userId = profile.userid;
+									newFlow.sender = true;
+									newFlow.deviceId = model.deviceId;
 									if(model.verificationStatus == VerificationStatus.VERIFIED){
 										newFlow.unverify();
 										deviceVerificationList.updateProfile(newFlow.userId);
 									}else{
 										deviceVerificationList.add(newFlow.tranId);
-										var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow});
+										var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest:false});
 										dialog.show();
 									}
 								}
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 8e74d1c..f40a7b8 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -100,7 +100,9 @@ ApplicationWindow {
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
                         }
-						onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.startVerificationRequest(); }
+						onClicked: { 
+							stack.replace(awaitingVerificationRequestAccept); 
+							isRequest?flow.sendVerificationRequest():flow.startVerificationRequest(); }
 					}
 				}
 			}
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index aba1f75..b97b6b3 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -562,12 +562,11 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(
           this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
         connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
-        connect(
-          this,
-          &ChatPage::tryDelayedSyncCb,
-          this,
-          [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
-          Qt::QueuedConnection);
+        connect(this,
+                &ChatPage::tryDelayedSyncCb,
+                this,
+                [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
+                Qt::QueuedConnection);
 
         connect(this,
                 &ChatPage::newSyncResponse,
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 0f521f9..5069ff9 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -22,6 +22,11 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
         this->sas           = olm::client()->sas_init();
         this->isMacVerified = false;
 
+        connect(this->model_,
+                &TimelineModel::updateFlowEventId,
+                this,
+                [this](std::string event_id) { this->relation.in_reply_to.event_id = event_id; });
+
         connect(timeout, &QTimer::timeout, this, [this]() {
                 emit timedout();
                 this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
@@ -222,6 +227,9 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
                         } else if (msg.relates_to.has_value()) {
+                                // this is just a workaround
+                                this->relation.in_reply_to.event_id =
+                                  msg.relates_to.value().in_reply_to.event_id;
                                 if (msg.relates_to.value().in_reply_to.event_id !=
                                     this->relation.in_reply_to.event_id)
                                         return;
@@ -343,11 +351,8 @@ DeviceVerificationFlow::setType(Type type)
 void
 DeviceVerificationFlow::setSender(bool sender_)
 {
-        this->sender = sender_;
-        if (this->sender == true && this->type == DeviceVerificationFlow::Type::ToDevice)
-                this->transaction_id = http::client()->generate_txn_id();
-        else if (this->sender == true && this->type == DeviceVerificationFlow::Type::RoomMsg)
-                this->relation.in_reply_to.event_id = http::client()->generate_txn_id();
+        this->sender         = sender_;
+        this->transaction_id = http::client()->generate_txn_id();
 }
 
 void
@@ -380,19 +385,16 @@ DeviceVerificationFlow::acceptVerificationRequest()
 
                 body[this->toClient][this->deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn(
-                                      "failed to accept verification request: {} {}",
-                                      err->matrix_error.error,
-                                      static_cast(err->status_code));
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to accept verification request: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 }
 //! responds verification request
@@ -410,18 +412,16 @@ DeviceVerificationFlow::sendVerificationReady()
 
                 body[this->toClient][this->deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn("failed to send verification ready: {} {}",
-                                                       err->matrix_error.error,
-                                                       static_cast(err->status_code));
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to send verification ready: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 }
 //! accepts a verification
@@ -436,18 +436,16 @@ DeviceVerificationFlow::sendVerificationDone()
 
                 body[this->toClient][this->deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn("failed to send verification done: {} {}",
-                                                       err->matrix_error.error,
-                                                       static_cast(err->status_code));
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to send verification done: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 }
 //! starts the verification flow
@@ -470,19 +468,16 @@ DeviceVerificationFlow::startVerificationRequest()
                 this->canonical_json                               = nlohmann::json(req);
                 body[this->toClient][this->deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [body](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn(
-                                      "failed to start verification request: {} {}",
-                                      err->matrix_error.error,
-                                      static_cast(err->status_code));
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [body](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to start verification request: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 }
 //! sends a verification request
@@ -505,17 +500,20 @@ DeviceVerificationFlow::sendVerificationRequest()
 
                 body[this->toClient][this->deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn("failed to send verification request: {} {}",
-                                                       err->matrix_error.error,
-                                                       static_cast(err->status_code));
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
-                (model_.value())->sendMessage(req);
+                http::client()->send_to_device(
+                  this->transaction_id, body, [](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to send verification request: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
+                req.to      = this->userId.toStdString();
+                req.msgtype = "m.key.verification.request";
+                req.body = "User is requesting to verify keys with you. However, your client does "
+                           "not support this method, so you will need to use the legacy method of "
+                           "key verification.";
+                (model_)->sendMessage(req);
         }
 }
 //! cancels a verification flow
@@ -552,21 +550,18 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c
 
                 body[this->toClient][deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [this](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn(
-                                      "failed to cancel verification request: {} {}",
-                                      err->matrix_error.error,
-                                      static_cast(err->status_code));
-
-                            this->deleteLater();
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [this](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to cancel verification request: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+
+                          this->deleteLater();
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 
         // TODO : Handle Blocking user better
@@ -595,18 +590,16 @@ DeviceVerificationFlow::sendVerificationKey()
 
                 body[this->toClient][deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn("failed to send verification key: {} {}",
-                                                       err->matrix_error.error,
-                                                       static_cast(err->status_code));
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to send verification key: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 }
 //! sends the mac of the keys
@@ -639,23 +632,21 @@ DeviceVerificationFlow::sendVerificationMac()
                 req.transaction_id                           = this->transaction_id;
                 body[this->toClient][deviceId.toStdString()] = req;
 
-                http::client()
-                  ->send_to_device(
-                    this->transaction_id, body, [this](mtx::http::RequestErr err) {
-                            if (err)
-                                    nhlog::net()->warn("failed to send verification MAC: {} {}",
-                                                       err->matrix_error.error,
-                                                       static_cast(err->status_code));
-
-                            if (this->isMacVerified == true)
-                                    this->acceptDevice();
-                            else
-                                    this->isMacVerified = true;
-                    });
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_.has_value()) {
+                http::client()->send_to_device(
+                  this->transaction_id, body, [this](mtx::http::RequestErr err) {
+                          if (err)
+                                  nhlog::net()->warn("failed to send verification MAC: {} {}",
+                                                     err->matrix_error.error,
+                                                     static_cast(err->status_code));
+
+                          if (this->isMacVerified == true)
+                                  this->acceptDevice();
+                          else
+                                  this->isMacVerified = true;
+                  });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
-                (model_.value())->sendMessage(req);
+                (model_)->sendMessage(req);
         }
 }
 //! Completes the verification flow
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index bec9f1e..1ad3b1d 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -126,6 +126,6 @@ private:
         // for room messages
         std::optional room_id;
         std::optional event_id;
-        std::optional model_;
+        TimelineModel *model_;
         mtx::common::ReplyRelatesTo relation;
 };
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index 869687f..24e2f35 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -37,8 +37,15 @@ struct EventMsgType
         template
         mtx::events::MessageType operator()(const mtx::events::Event &e)
         {
-                if constexpr (is_detected::value)
-                        return mtx::events::getMessageType(e.content.msgtype);
+                if constexpr (is_detected::value) {
+                        if constexpr (std::is_same_v,
+                                                     std::remove_cv_t>)
+                                return mtx::events::getMessageType(e.content.msgtype.value());
+                        else if constexpr (std::is_same_v<
+                                             std::string,
+                                             std::remove_cv_t>)
+                                return mtx::events::getMessageType(e.content.msgtype);
+                }
                 return mtx::events::MessageType::Unknown;
         }
 };
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 639cae0..208b20e 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -5,6 +5,7 @@
 
 #include "Cache.h"
 #include "Cache_p.h"
+#include "ChatPage.h"
 #include "EventAccessors.h"
 #include "Logging.h"
 #include "MatrixClient.h"
@@ -31,41 +32,38 @@ EventStore::EventStore(std::string room_id, QObject *)
                 this->last  = range->last;
         }
 
-        connect(
-          this,
-          &EventStore::eventFetched,
-          this,
-          [this](std::string id,
-                 std::string relatedTo,
-                 mtx::events::collections::TimelineEvents timeline) {
-                  cache::client()->storeEvent(room_id_, id, {timeline});
-
-                  if (!relatedTo.empty()) {
-                          auto idx = idToIndex(relatedTo);
-                          if (idx)
-                                  emit dataChanged(*idx, *idx);
-                  }
-          },
-          Qt::QueuedConnection);
-
-        connect(
-          this,
-          &EventStore::oldMessagesRetrieved,
-          this,
-          [this](const mtx::responses::Messages &res) {
-                  //
-                  uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
-                  if (newFirst == first)
-                          fetchMore();
-                  else {
-                          emit beginInsertRows(toExternalIdx(newFirst),
-                                               toExternalIdx(this->first - 1));
-                          this->first = newFirst;
-                          emit endInsertRows();
-                          emit fetchedMore();
-                  }
-          },
-          Qt::QueuedConnection);
+        connect(this,
+                &EventStore::eventFetched,
+                this,
+                [this](std::string id,
+                       std::string relatedTo,
+                       mtx::events::collections::TimelineEvents timeline) {
+                        cache::client()->storeEvent(room_id_, id, {timeline});
+
+                        if (!relatedTo.empty()) {
+                                auto idx = idToIndex(relatedTo);
+                                if (idx)
+                                        emit dataChanged(*idx, *idx);
+                        }
+                },
+                Qt::QueuedConnection);
+
+        connect(this,
+                &EventStore::oldMessagesRetrieved,
+                this,
+                [this](const mtx::responses::Messages &res) {
+                        uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
+                        if (newFirst == first)
+                                fetchMore();
+                        else {
+                                emit beginInsertRows(toExternalIdx(newFirst),
+                                                     toExternalIdx(this->first - 1));
+                                this->first = newFirst;
+                                emit endInsertRows();
+                                emit fetchedMore();
+                        }
+                },
+                Qt::QueuedConnection);
 
         connect(this, &EventStore::processPending, this, [this]() {
                 if (!current_txn.empty()) {
@@ -116,48 +114,46 @@ EventStore::EventStore(std::string room_id, QObject *)
                   event->data);
         });
 
-        connect(
-          this,
-          &EventStore::messageFailed,
-          this,
-          [this](std::string txn_id) {
-                  if (current_txn == txn_id) {
-                          current_txn_error_count++;
-                          if (current_txn_error_count > 10) {
-                                  nhlog::ui()->debug("failing txn id '{}'", txn_id);
-                                  cache::client()->removePendingStatus(room_id_, txn_id);
-                                  current_txn_error_count = 0;
-                          }
-                  }
-                  QTimer::singleShot(1000, this, [this]() {
-                          nhlog::ui()->debug("timeout");
-                          this->current_txn = "";
-                          emit processPending();
-                  });
-          },
-          Qt::QueuedConnection);
-
-        connect(
-          this,
-          &EventStore::messageSent,
-          this,
-          [this](std::string txn_id, std::string event_id) {
-                  nhlog::ui()->debug("sent {}", txn_id);
-
-                  http::client()->read_event(
-                    room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
-                            if (err) {
-                                    nhlog::net()->warn(
-                                      "failed to read_event ({}, {})", room_id_, event_id);
-                            }
-                    });
-
-                  cache::client()->removePendingStatus(room_id_, txn_id);
-                  this->current_txn             = "";
-                  this->current_txn_error_count = 0;
-                  emit processPending();
-          },
-          Qt::QueuedConnection);
+        connect(this,
+                &EventStore::messageFailed,
+                this,
+                [this](std::string txn_id) {
+                        if (current_txn == txn_id) {
+                                current_txn_error_count++;
+                                if (current_txn_error_count > 10) {
+                                        nhlog::ui()->debug("failing txn id '{}'", txn_id);
+                                        cache::client()->removePendingStatus(room_id_, txn_id);
+                                        current_txn_error_count = 0;
+                                }
+                        }
+                        QTimer::singleShot(1000, this, [this]() {
+                                nhlog::ui()->debug("timeout");
+                                this->current_txn = "";
+                                emit processPending();
+                        });
+                },
+                Qt::QueuedConnection);
+
+        connect(this,
+                &EventStore::messageSent,
+                this,
+                [this](std::string txn_id, std::string event_id) {
+                        nhlog::ui()->debug("sent {}", txn_id);
+
+                        http::client()->read_event(
+                          room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
+                                  if (err) {
+                                          nhlog::net()->warn(
+                                            "failed to read_event ({}, {})", room_id_, event_id);
+                                  }
+                          });
+
+                        cache::client()->removePendingStatus(room_id_, txn_id);
+                        this->current_txn             = "";
+                        this->current_txn_error_count = 0;
+                        emit processPending();
+                },
+                Qt::QueuedConnection);
 }
 
 void
@@ -245,6 +241,58 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                                 emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
                         }
                 }
+
+                // decrypting and checking some encrypted messages
+                if (auto encrypted =
+                      std::get_if>(
+                        &event)) {
+                        auto event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+                        if (std::visit(
+                              [](auto e) { return (e.sender != utils::localUser().toStdString()); },
+                              *event)) {
+                                if (auto msg = std::get_if>(event)) {
+                                        last_verification_request_event = *msg;
+                                } else if (auto msg = std::get_if>(event)) {
+                                        last_verification_cancel_event = *msg;
+                                        ChatPage::instance()->recievedDeviceVerificationCancel(
+                                          msg->content);
+                                } else if (auto msg = std::get_if>(event)) {
+                                        ChatPage::instance()->recievedDeviceVerificationAccept(
+                                          msg->content);
+                                } else if (auto msg = std::get_if>(event)) {
+                                        ChatPage::instance()->recievedDeviceVerificationKey(
+                                          msg->content);
+                                } else if (auto msg = std::get_if>(event)) {
+                                        ChatPage::instance()->recievedDeviceVerificationMac(
+                                          msg->content);
+                                } else if (auto msg = std::get_if>(event)) {
+                                        ChatPage::instance()->recievedDeviceVerificationReady(
+                                          msg->content);
+                                } else if (auto msg = std::get_if>(event)) {
+                                        ChatPage::instance()->recievedDeviceVerificationDone(
+                                          msg->content);
+                                } else if (auto msg = std::get_if>(event)) {
+                                        ChatPage::instance()->recievedDeviceVerificationStart(
+                                          msg->content, msg->sender);
+                                }
+                        }
+                }
+        }
+
+        if (last_verification_request_event.has_value()) {
+                if (last_verification_request_event.value().origin_server_ts >
+                    last_verification_cancel_event.origin_server_ts) {
+                        emit startDMVerification(last_verification_request_event.value());
+                        last_verification_request_event = {};
+                }
         }
 }
 
@@ -425,7 +473,8 @@ EventStore::decryptEvent(const IdIndex &idx,
                                       e.what());
                 dummy.content.body =
                   tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
-                     "Placeholder, when the message can't be decrypted, because the DB access "
+                     "Placeholder, when the message can't be decrypted, because the DB "
+                     "access "
                      "failed.")
                     .toStdString();
                 return asCacheEntry(std::move(dummy));
@@ -437,7 +486,8 @@ EventStore::decryptEvent(const IdIndex &idx,
                                           e.what());
                 dummy.content.body =
                   tr("-- Decryption Error (%1) --",
-                     "Placeholder, when the message can't be decrypted. In this case, the Olm "
+                     "Placeholder, when the message can't be decrypted. In this case, the "
+                     "Olm "
                      "decrytion returned an error, which is passed as %1.")
                     .arg(e.what())
                     .toStdString();
@@ -470,11 +520,11 @@ EventStore::decryptEvent(const IdIndex &idx,
                 return asCacheEntry(std::move(temp_events[0]));
         }
 
-        dummy.content.body =
-          tr("-- Encrypted Event (Unknown event type) --",
-             "Placeholder, when the message was decrypted, but we couldn't parse it, because "
-             "Nheko/mtxclient don't support that event type yet.")
-            .toStdString();
+        dummy.content.body = tr("-- Encrypted Event (Unknown event type) --",
+                                "Placeholder, when the message was decrypted, but we "
+                                "couldn't parse it, because "
+                                "Nheko/mtxclient don't support that event type yet.")
+                               .toStdString();
         return asCacheEntry(std::move(dummy));
 }
 
@@ -502,7 +552,8 @@ EventStore::get(std::string_view id, std::string_view related_to, bool decrypt)
                                           mtx::http::RequestErr err) {
                                   if (err) {
                                           nhlog::net()->error(
-                                            "Failed to retrieve event with id {}, which was "
+                                            "Failed to retrieve event with id {}, which "
+                                            "was "
                                             "requested to show the replyTo for event {}",
                                             relatedTo,
                                             id);
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index b5c17d1..28d46e9 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -98,6 +98,8 @@ signals:
         void processPending();
         void messageSent(std::string txn_id, std::string event_id);
         void messageFailed(std::string txn_id);
+        void startDMVerification(
+          mtx::events::RoomEvent &msg);
 
 public slots:
         void addPending(mtx::events::collections::TimelineEvents event);
@@ -118,4 +120,10 @@ private:
 
         std::string current_txn;
         int current_txn_error_count = 0;
+
+        // probably not the best way to do
+        std::optional>
+          last_verification_request_event;
+        mtx::events::RoomEvent
+          last_verification_cancel_event;
 };
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index adf207a..809fe38 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -186,12 +186,11 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
   , room_id_(room_id)
   , manager_(manager)
 {
-        connect(
-          this,
-          &TimelineModel::redactionFailed,
-          this,
-          [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
-          Qt::QueuedConnection);
+        connect(this,
+                &TimelineModel::redactionFailed,
+                this,
+                [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
+                Qt::QueuedConnection);
 
         connect(this,
                 &TimelineModel::newMessageToSend,
@@ -200,17 +199,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 Qt::QueuedConnection);
         connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
 
-        connect(
-          &events,
-          &EventStore::dataChanged,
-          this,
-          [this](int from, int to) {
-                  nhlog::ui()->debug(
-                    "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
-                  emit dataChanged(index(events.size() - to - 1, 0),
-                                   index(events.size() - from - 1, 0));
-          },
-          Qt::QueuedConnection);
+        connect(&events,
+                &EventStore::dataChanged,
+                this,
+                [this](int from, int to) {
+                        nhlog::ui()->debug("data changed {} to {}",
+                                           events.size() - to - 1,
+                                           events.size() - from - 1);
+                        emit dataChanged(index(events.size() - to - 1, 0),
+                                         index(events.size() - from - 1, 0));
+                },
+                Qt::QueuedConnection);
 
         connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
                 int first = events.size() - to;
@@ -232,6 +231,12 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
         connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage);
         connect(
           &events, &EventStore::fetchedMore, this, [this]() { setPaginationInProgress(false); });
+        connect(&events,
+                &EventStore::startDMVerification,
+                this,
+                [this](mtx::events::RoomEvent msg) {
+                        ChatPage::instance()->recievedRoomDeviceVerificationRequest(msg, this);
+                });
 }
 
 QHash
@@ -613,187 +618,6 @@ TimelineModel::updateLastMessage()
         }
 }
 
-std::vector
-TimelineModel::internalAddEvents(
-  const std::vector &timeline)
-{
-        std::vector ids;
-        for (auto e : timeline) {
-                QString id = QString::fromStdString(mtx::accessors::event_id(e));
-
-                if (this->events.contains(id)) {
-                        this->events.insert(id, e);
-                        int idx = idToIndex(id);
-                        emit dataChanged(index(idx, 0), index(idx, 0));
-                        continue;
-                }
-
-                QString txid = QString::fromStdString(mtx::accessors::transaction_id(e));
-                if (this->pending.removeOne(txid)) {
-                        this->events.insert(id, e);
-                        this->events.remove(txid);
-                        int idx = idToIndex(txid);
-                        if (idx < 0) {
-                                nhlog::ui()->warn("Received index out of range");
-                                continue;
-                        }
-                        eventOrder[idx] = id;
-                        emit dataChanged(index(idx, 0), index(idx, 0));
-                        continue;
-                }
-
-                if (auto redaction =
-                      std::get_if>(&e)) {
-                        QString redacts = QString::fromStdString(redaction->redacts);
-                        auto redacted   = std::find(eventOrder.begin(), eventOrder.end(), redacts);
-
-                        auto event = events.value(redacts);
-                        if (auto reaction =
-                              std::get_if>(
-                                &event)) {
-                                QString reactedTo =
-                                  QString::fromStdString(reaction->content.relates_to.event_id);
-                                reactions[reactedTo].removeReaction(*reaction);
-                                int idx = idToIndex(reactedTo);
-                                if (idx >= 0)
-                                        emit dataChanged(index(idx, 0), index(idx, 0));
-                        }
-
-                        if (redacted != eventOrder.end()) {
-                                auto redactedEvent = std::visit(
-                                  [](const auto &ev)
-                                    -> mtx::events::RoomEvent {
-                                          mtx::events::RoomEvent
-                                            replacement                = {};
-                                          replacement.event_id         = ev.event_id;
-                                          replacement.room_id          = ev.room_id;
-                                          replacement.sender           = ev.sender;
-                                          replacement.origin_server_ts = ev.origin_server_ts;
-                                          replacement.type             = ev.type;
-                                          return replacement;
-                                  },
-                                  e);
-                                events.insert(redacts, redactedEvent);
-
-                                int row = (int)std::distance(eventOrder.begin(), redacted);
-                                emit dataChanged(index(row, 0), index(row, 0));
-                        }
-
-                        continue; // don't insert redaction into timeline
-                }
-
-                if (auto reaction =
-                      std::get_if>(&e)) {
-                        QString reactedTo =
-                          QString::fromStdString(reaction->content.relates_to.event_id);
-                        events.insert(id, e);
-
-                        // remove local echo
-                        if (!txid.isEmpty()) {
-                                auto rCopy     = *reaction;
-                                rCopy.event_id = txid.toStdString();
-                                reactions[reactedTo].removeReaction(rCopy);
-                        }
-
-                        reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction);
-                        int idx = idToIndex(reactedTo);
-                        if (idx >= 0)
-                                emit dataChanged(index(idx, 0), index(idx, 0));
-                        continue; // don't insert reaction into timeline
-                }
-
-                if (auto event =
-                      std::get_if>(&e)) {
-                        auto e_      = decryptEvent(*event).event;
-                        auto encInfo = mtx::accessors::file(e_);
-
-                        if (encInfo)
-                                emit newEncryptedImage(encInfo.value());
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(
-                              &e_)) {
-                                last_verification_request_event = *msg;
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(
-                              &e_)) {
-                                last_verification_cancel_event = *msg;
-                                ChatPage::instance()->recievedDeviceVerificationCancel(
-                                  msg->content);
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(
-                              &e_)) {
-                                ChatPage::instance()->recievedDeviceVerificationAccept(
-                                  msg->content);
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(&e_)) {
-                                ChatPage::instance()->recievedDeviceVerificationKey(msg->content);
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(&e_)) {
-                                ChatPage::instance()->recievedDeviceVerificationMac(msg->content);
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(
-                              &e_)) {
-                                ChatPage::instance()->recievedDeviceVerificationReady(msg->content);
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(&e_)) {
-                                ChatPage::instance()->recievedDeviceVerificationDone(msg->content);
-                        }
-
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(
-                              &e_)) {
-                                ChatPage::instance()->recievedDeviceVerificationStart(msg->content,
-                                                                                      msg->sender);
-                        }
-                }
-
-                this->events.insert(id, e);
-                ids.push_back(id);
-
-                auto replyTo  = mtx::accessors::in_reply_to_event(e);
-                auto qReplyTo = QString::fromStdString(replyTo);
-                if (!replyTo.empty() && !events.contains(qReplyTo)) {
-                        http::client()->get_event(
-                          this->room_id_.toStdString(),
-                          replyTo,
-                          [this, id, replyTo](
-                            const mtx::events::collections::TimelineEvents &timeline,
-                            mtx::http::RequestErr err) {
-                                  if (err) {
-                                          nhlog::net()->error(
-                                            "Failed to retrieve event with id {}, which was "
-                                            "requested to show the replyTo for event {}",
-                                            replyTo,
-                                            id.toStdString());
-                                          return;
-                                  }
-                                  emit eventFetched(id, timeline);
-                          });
-                }
-        }
-
-        if (last_verification_request_event.origin_server_ts >
-            last_verification_cancel_event.origin_server_ts) {
-                ChatPage::instance()->recievedRoomDeviceVerificationRequest(
-                  last_verification_request_event, this);
-        }
-
-        return ids;
-}
-
 void
 TimelineModel::setCurrentIndex(int index)
 {
@@ -979,15 +803,18 @@ TimelineModel::markEventsAsRead(const std::vector &event_ids)
         }
 }
 
+template
 void
-TimelineModel::sendEncryptedMessage(const std::string txn_id, nlohmann::json content)
+TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg)
 {
         const auto room_id = room_id_.toStdString();
 
         using namespace mtx::events;
         using namespace mtx::identifiers;
 
-        json doc = {{"type", "m.room.message"}, {"content", content}, {"room_id", room_id}};
+        json doc = {
+          {"type", to_string(msg.type)}, {"content", json(msg.content)}, {"room_id", room_id}};
+        std::cout << doc.dump(2) << std::endl;
 
         try {
                 // Check if we have already an outbound megolm session then we can use.
@@ -995,7 +822,7 @@ TimelineModel::sendEncryptedMessage(const std::string txn_id, nlohmann::json con
                         mtx::events::EncryptedEvent event;
                         event.content =
                           olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
-                        event.event_id         = txn_id;
+                        event.event_id         = msg.event_id;
                         event.room_id          = room_id;
                         event.sender           = http::client()->user_id().to_string();
                         event.type             = mtx::events::EventType::RoomEncrypted;
@@ -1030,25 +857,26 @@ TimelineModel::sendEncryptedMessage(const std::string txn_id, nlohmann::json con
                 const auto members = cache::roomMembers(room_id);
                 nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id);
 
-                auto keeper = std::make_shared([room_id, doc, txn_id, this]() {
-                        try {
-                                mtx::events::EncryptedEvent event;
-                                event.content = olm::encrypt_group_message(
-                                  room_id, http::client()->device_id(), doc);
-                                event.event_id         = txn_id;
-                                event.room_id          = room_id;
-                                event.sender           = http::client()->user_id().to_string();
-                                event.type             = mtx::events::EventType::RoomEncrypted;
-                                event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
-
-                                emit this->addPendingMessageToStore(event);
-                        } catch (const lmdb::error &e) {
-                                nhlog::db()->critical("failed to save megolm outbound session: {}",
-                                                      e.what());
-                                emit ChatPage::instance()->showNotification(
-                                  tr("Failed to encrypt event, sending aborted!"));
-                        }
-                });
+                auto keeper =
+                  std::make_shared([room_id, doc, txn_id = msg.event_id, this]() {
+                          try {
+                                  mtx::events::EncryptedEvent event;
+                                  event.content = olm::encrypt_group_message(
+                                    room_id, http::client()->device_id(), doc);
+                                  event.event_id         = txn_id;
+                                  event.room_id          = room_id;
+                                  event.sender           = http::client()->user_id().to_string();
+                                  event.type             = mtx::events::EventType::RoomEncrypted;
+                                  event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
+
+                                  emit this->addPendingMessageToStore(event);
+                          } catch (const lmdb::error &e) {
+                                  nhlog::db()->critical(
+                                    "failed to save megolm outbound session: {}", e.what());
+                                  emit ChatPage::instance()->showNotification(
+                                    tr("Failed to encrypt event, sending aborted!"));
+                          }
+                  });
 
                 mtx::requests::QueryKeys req;
                 for (const auto &member : members)
@@ -1056,7 +884,7 @@ TimelineModel::sendEncryptedMessage(const std::string txn_id, nlohmann::json con
 
                 http::client()->query_keys(
                   req,
-                  [keeper = std::move(keeper), megolm_payload, txn_id, this](
+                  [keeper = std::move(keeper), megolm_payload, txn_id = msg.event_id, this](
                     const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
                           if (err) {
                                   nhlog::net()->warn("failed to query device keys: {} {}",
@@ -1265,6 +1093,40 @@ struct SendMessageVisitor
           : model_(model)
         {}
 
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                emit model_->updateFlowEventId(msg.event_id);
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+        void operator()(const mtx::events::RoomEvent &msg)
+        {
+                model_->sendEncryptedMessage(msg);
+        }
+
         // Do-nothing operator for all unhandled events
         template
         void operator()(const mtx::events::Event &)
@@ -1280,7 +1142,7 @@ struct SendMessageVisitor
                         if (encInfo)
                                 emit model_->newEncryptedImage(encInfo.value());
 
-                        model_->sendEncryptedMessage(msg.event_id, nlohmann::json(msg.content));
+                        model_->sendEncryptedMessage(msg);
                 } else {
                         emit model_->addPendingMessageToStore(msg);
                 }
@@ -1300,20 +1162,6 @@ struct SendMessageVisitor
         TimelineModel *model_;
 };
 
-void
-TimelineModel::processOnePendingMessage()
-{
-        if (pending.isEmpty())
-                return;
-
-        QString txn_id_qstr = pending.first();
-
-        auto event = events.value(txn_id_qstr);
-        std::cout << "Inside the process one pending message" << std::endl;
-        std::cout << std::visit([](auto &e) { return json(e); }, event).dump(2) << std::endl;
-        std::visit(SendMessageVisitor{txn_id_qstr, this}, event);
-}
-
 void
 TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
 {
@@ -1359,18 +1207,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
                   event);
         }
 
-        internalAddEvents({event});
-
-        QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event));
-        pending.push_back(txn_id_qstr);
-        if (!std::get_if>(&event)) {
-                beginInsertRows(QModelIndex(), 0, 0);
-                this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr);
-                endInsertRows();
-        }
-        updateLastMessage();
-
-        emit nextPendingMessage();
+        std::visit(SendMessageVisitor{this}, event);
 }
 
 bool
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 1b6f999..fb9921d 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -9,12 +9,8 @@
 #include 
 
 #include "CacheCryptoStructs.h"
-<<<<<<< HEAD
 #include "EventStore.h"
-=======
-#include "ReactionsModel.h"
 #include "ui/UserProfile.h"
->>>>>>> Refactor UserProfile
 
 namespace mtx::http {
 using RequestErr = const std::optional &;
@@ -271,8 +267,13 @@ signals:
 
         void openProfile(UserProfile *profile);
 
+        void newMessageToSend(mtx::events::collections::TimelineEvents event);
+        void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
+        void updateFlowEventId(std::string event_id);
+
 private:
-        void sendEncryptedMessage(const std::string txn_id, nlohmann::json content);
+        template
+        void sendEncryptedMessage(mtx::events::RoomEvent msg);
         void handleClaimedKeys(std::shared_ptr keeper,
                                const std::map &room_key,
                                const std::map &pks,
@@ -297,11 +298,6 @@ private:
         std::vector typingUsers_;
 
         TimelineViewManager *manager_;
-        // probably not the best way to do
-        mtx::events::RoomEvent
-          last_verification_request_event;
-        mtx::events::RoomEvent
-          last_verification_cancel_event;
 
         friend struct SendMessageVisitor;
 };
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 3499384..1eaa9d2 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -5,13 +5,15 @@
 #include "Logging.h"
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
+#include "timeline/TimelineModel.h"
 
 #include  // only for debugging
 
-UserProfile::UserProfile(QString roomid, QString userid, QObject *parent)
+UserProfile::UserProfile(QString roomid, QString userid, TimelineModel *parent)
   : QObject(parent)
   , roomid_(roomid)
   , userid_(userid)
+  , model(parent)
 {
         fetchDeviceList(this->userid_);
 }
@@ -185,27 +187,43 @@ UserProfile::startChat()
         emit ChatPage::instance()->createRoom(req);
 }
 
-void
-UserProfile::verifyUser()
+DeviceVerificationFlow *
+UserProfile::createFlow(bool isVerifyUser)
 {
-        std::cout << "Checking if to start to device verification or room message verification"
-                  << std::endl;
-        auto joined_rooms = cache::joinedRooms();
-        auto room_infos   = cache::getRoomInfo(joined_rooms);
-
-        for (std::string room_id : joined_rooms) {
-                if ((room_infos[QString::fromStdString(room_id)].member_count == 2) &&
-                    cache::isRoomEncrypted(room_id)) {
-                        auto room_members = cache::roomMembers(room_id);
-                        if (std::find(room_members.begin(),
-                                      room_members.end(),
-                                      (this->userid()).toStdString()) != room_members.end()) {
-                                std::cout << "FOUND A ENCRYPTED ROOM WITH THIS USER : " << room_id
+        if (!isVerifyUser)
+                return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice));
+        else {
+                std::cout << "CHECKING IF IT TO START ROOM_VERIFICATION OR TO_DEVICE VERIFICATION"
+                          << std::endl;
+                auto joined_rooms = cache::joinedRooms();
+                auto room_infos   = cache::getRoomInfo(joined_rooms);
+
+                for (std::string room_id : joined_rooms) {
+                        if ((room_infos[QString::fromStdString(room_id)].member_count == 2) &&
+                            cache::isRoomEncrypted(room_id)) {
+                                auto room_members = cache::roomMembers(room_id);
+                                if (std::find(room_members.begin(),
+                                              room_members.end(),
+                                              (this->userid()).toStdString()) !=
+                                    room_members.end()) {
+                                        std::cout
+                                          << "FOUND A ENCRYPTED ROOM WITH THIS USER : " << room_id
                                           << std::endl;
-                                return;
+                                        if (this->roomid_.toStdString() == room_id) {
+                                                auto newflow = new DeviceVerificationFlow(
+                                                  this, DeviceVerificationFlow::Type::RoomMsg);
+                                                newflow->setModel(this->model);
+                                                return (std::move(newflow));
+                                        } else {
+                                                std::cout << "FOUND A ENCRYPTED ROOM BUT CURRENTLY "
+                                                             "NOT IN THAT ROOM : "
+                                                          << room_id << std::endl;
+                                        }
+                                }
                         }
                 }
-        }
 
-        std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl;
+                std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl;
+                return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice));
+        }
 }
\ No newline at end of file
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 3f9cbe6..3d0d298 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -20,6 +20,7 @@ Q_ENUM_NS(Status)
 }
 
 class DeviceVerificationFlow;
+class TimelineModel;
 
 class DeviceInfo
 {
@@ -83,7 +84,7 @@ class UserProfile : public QObject
         Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
         Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT)
 public:
-        UserProfile(QString roomid, QString userid, QObject *parent = 0);
+        UserProfile(QString roomid, QString userid, TimelineModel *parent = nullptr);
 
         DeviceInfoModel *deviceList();
 
@@ -92,18 +93,19 @@ public:
         QString avatarUrl();
         bool getUserStatus();
 
+        Q_INVOKABLE DeviceVerificationFlow *createFlow(bool isVerifyUser);
         Q_INVOKABLE void fetchDeviceList(const QString &userID);
         Q_INVOKABLE void banUser();
         // Q_INVOKABLE void ignoreUser();
         Q_INVOKABLE void kickUser();
         Q_INVOKABLE void startChat();
-        Q_INVOKABLE void verifyUser();
 
 private:
         QString roomid_, userid_;
         std::optional cross_verified;
         DeviceInfoModel deviceList_;
         bool isUserVerified = false;
+        TimelineModel *model;
 
         void callback_fn(const mtx::responses::QueryKeys &res,
                          mtx::http::RequestErr err,

From 8a4bd37fead20e24876ec9ce703cabb041ec67ba Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Tue, 18 Aug 2020 11:29:02 +0530
Subject: [PATCH 31/70] [WIP] Room Verification Works!

---
 resources/qml/UserProfile.qml        |  2 +-
 src/DeviceVerificationFlow.cpp       | 71 ++++++++++++++--------------
 src/DeviceVerificationFlow.h         |  6 +--
 src/Olm.cpp                          | 18 ++++---
 src/timeline/EventStore.cpp          | 22 +++++++--
 src/timeline/EventStore.h            |  1 +
 src/timeline/TimelineModel.cpp       |  7 ++-
 src/timeline/TimelineViewManager.cpp | 71 +++++++++++++++-------------
 src/ui/UserProfile.cpp               |  5 +-
 9 files changed, 114 insertions(+), 89 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 9b53ff3..115a73c 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -14,7 +14,7 @@ ApplicationWindow{
 	height: 650
 	width: 420
 	modality: Qt.WindowModal
-	Layout.alignment: Qt.AlignHCenter
+	Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
 	palette: colors
 
 	Connections{
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 5069ff9..8c23088 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -8,24 +8,31 @@
 #include 
 #include 
 
-#include 
-
 static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
 
 namespace msgs = mtx::events::msg;
 
-DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type)
+DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
+                                               DeviceVerificationFlow::Type flow_type,
+                                               TimelineModel *model)
   : type(flow_type)
+  , model_(model)
 {
         timeout = new QTimer(this);
         timeout->setSingleShot(true);
         this->sas           = olm::client()->sas_init();
         this->isMacVerified = false;
 
-        connect(this->model_,
-                &TimelineModel::updateFlowEventId,
-                this,
-                [this](std::string event_id) { this->relation.in_reply_to.event_id = event_id; });
+        if (model) {
+                connect(this->model_,
+                        &TimelineModel::updateFlowEventId,
+                        this,
+                        [this](std::string event_id) {
+                                this->relation.rel_type = mtx::common::RelationType::Reference;
+                                this->relation.event_id = event_id;
+                                this->transaction_id    = event_id;
+                        });
+        }
 
         connect(timeout, &QTimer::timeout, this, [this]() {
                 emit timedout();
@@ -42,8 +49,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                           if (msg.transaction_id.value() != this->transaction_id)
                                   return;
                   } else if (msg.relates_to.has_value()) {
-                          if (msg.relates_to.value().in_reply_to.event_id !=
-                              this->relation.in_reply_to.event_id)
+                          if (msg.relates_to.value().event_id != this->relation.event_id)
                                   return;
                   }
                   if ((std::find(msg.key_agreement_protocols.begin(),
@@ -69,8 +75,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                     DeviceVerificationFlow::Error::UnknownMethod);
                                   return;
                           }
-                          this->acceptVerificationRequest();
                           this->canonical_json = nlohmann::json(msg);
+                          this->acceptVerificationRequest();
                   } else {
                           this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
                   }
@@ -84,8 +90,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
                         } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().in_reply_to.event_id !=
-                                    this->relation.in_reply_to.event_id)
+                                if (msg.relates_to.value().event_id != this->relation.event_id)
                                         return;
                         }
                         if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
@@ -116,8 +121,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
                         } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().in_reply_to.event_id !=
-                                    this->relation.in_reply_to.event_id)
+                                if (msg.relates_to.value().event_id != this->relation.event_id)
                                         return;
                         }
                         emit verificationCanceled();
@@ -131,8 +135,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
                         } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().in_reply_to.event_id !=
-                                    this->relation.in_reply_to.event_id)
+                                if (msg.relates_to.value().event_id != this->relation.event_id)
                                         return;
                         }
                         this->sas->set_their_key(msg.key);
@@ -157,6 +160,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                         } else if (this->method == DeviceVerificationFlow::Method::Decimal) {
                                 this->sasList = this->sas->generate_bytes_decimal(info);
                         }
+
                         if (this->sender == false) {
                                 emit this->verificationRequestAccepted(this->method);
                                 this->sendVerificationKey();
@@ -181,8 +185,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                           if (msg.transaction_id.value() != this->transaction_id)
                                   return;
                   } else if (msg.relates_to.has_value()) {
-                          if (msg.relates_to.value().in_reply_to.event_id !=
-                              this->relation.in_reply_to.event_id)
+                          if (msg.relates_to.value().event_id != this->relation.event_id)
                                   return;
                   }
                   std::string info = "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() +
@@ -227,12 +230,11 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
                         } else if (msg.relates_to.has_value()) {
-                                // this is just a workaround
-                                this->relation.in_reply_to.event_id =
-                                  msg.relates_to.value().in_reply_to.event_id;
-                                if (msg.relates_to.value().in_reply_to.event_id !=
-                                    this->relation.in_reply_to.event_id)
+                                if (msg.relates_to.value().event_id != this->relation.event_id)
                                         return;
+                                else {
+                                        this->deviceId = QString::fromStdString(msg.from_device);
+                                }
                         }
                         this->startVerificationRequest();
                 });
@@ -245,8 +247,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
                         } else if (msg.relates_to.has_value()) {
-                                if (msg.relates_to.value().in_reply_to.event_id !=
-                                    this->relation.in_reply_to.event_id)
+                                if (msg.relates_to.value().event_id != this->relation.event_id)
                                         return;
                         }
                         this->acceptDevice();
@@ -297,12 +298,6 @@ DeviceVerificationFlow::getSasList()
         return this->sasList;
 }
 
-void
-DeviceVerificationFlow::setModel(TimelineModel *&model)
-{
-        this->model_ = model;
-}
-
 void
 DeviceVerificationFlow::setTransactionId(QString transaction_id_)
 {
@@ -351,15 +346,17 @@ DeviceVerificationFlow::setType(Type type)
 void
 DeviceVerificationFlow::setSender(bool sender_)
 {
-        this->sender         = sender_;
-        this->transaction_id = http::client()->generate_txn_id();
+        this->sender = sender_;
+        if (this->sender)
+                this->transaction_id = http::client()->generate_txn_id();
 }
 
 void
 DeviceVerificationFlow::setEventId(std::string event_id)
 {
-        this->relation.in_reply_to.event_id = event_id;
-        this->transaction_id                = event_id;
+        this->relation.rel_type = mtx::common::RelationType::Reference;
+        this->relation.event_id = event_id;
+        this->transaction_id    = event_id;
 }
 
 //! accepts a verification
@@ -476,7 +473,8 @@ DeviceVerificationFlow::startVerificationRequest()
                                                      static_cast(err->status_code));
                   });
         } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
-                req.relates_to = this->relation;
+                req.relates_to       = this->relation;
+                this->canonical_json = nlohmann::json(req);
                 (model_)->sendMessage(req);
         }
 }
@@ -562,6 +560,7 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c
         } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                 req.relates_to = this->relation;
                 (model_)->sendMessage(req);
+                this->deleteLater();
         }
 
         // TODO : Handle Blocking user better
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 1ad3b1d..4c3e517 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -52,7 +52,8 @@ public:
 
         DeviceVerificationFlow(
           QObject *parent              = nullptr,
-          DeviceVerificationFlow::Type = DeviceVerificationFlow::Type::ToDevice);
+          DeviceVerificationFlow::Type = DeviceVerificationFlow::Type::ToDevice,
+          TimelineModel *model         = nullptr);
         // getters
         QString getTransactionId();
         QString getUserId();
@@ -62,7 +63,6 @@ public:
         std::vector getSasList();
         bool getSender();
         // setters
-        void setModel(TimelineModel *&model);
         void setTransactionId(QString transaction_id_);
         void setUserId(QString userID);
         void setDeviceId(QString deviceID);
@@ -127,5 +127,5 @@ private:
         std::optional room_id;
         std::optional event_id;
         TimelineModel *model_;
-        mtx::common::ReplyRelatesTo relation;
+        mtx::common::ReactionRelatesTo relation;
 };
diff --git a/src/Olm.cpp b/src/Olm.cpp
index ff6ea2f..48439fa 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -211,10 +211,15 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
 
         // relations shouldn't be encrypted...
         mtx::common::ReplyRelatesTo relation;
+        mtx::common::ReactionRelatesTo r_relation;
+
         if (body["content"].contains("m.relates_to") &&
             body["content"]["m.relates_to"].contains("m.in_reply_to")) {
                 relation = body["content"]["m.relates_to"];
                 body["content"].erase("m.relates_to");
+        } else if (body["content"]["m.relates_to"].contains("event_id")) {
+                r_relation = body["content"]["m.relates_to"];
+                body["content"].erase("m.relates_to");
         }
 
         // Always check before for existence.
@@ -223,12 +228,13 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
 
         // Prepare the m.room.encrypted event.
         msg::Encrypted data;
-        data.ciphertext = std::string((char *)payload.data(), payload.size());
-        data.sender_key = olm::client()->identity_keys().curve25519;
-        data.session_id = res.data.session_id;
-        data.device_id  = device_id;
-        data.algorithm  = MEGOLM_ALGO;
-        data.relates_to = relation;
+        data.ciphertext   = std::string((char *)payload.data(), payload.size());
+        data.sender_key   = olm::client()->identity_keys().curve25519;
+        data.session_id   = res.data.session_id;
+        data.device_id    = device_id;
+        data.algorithm    = MEGOLM_ALGO;
+        data.relates_to   = relation;
+        data.r_relates_to = r_relation;
 
         auto message_index = olm_outbound_group_session_message_index(res.session);
         nhlog::crypto()->debug("next message_index {}", message_index);
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 208b20e..b210e15 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -95,8 +95,8 @@ EventStore::EventStore(std::string room_id, QObject *)
                                     room_id_,
                                     txn_id,
                                     e.content,
-                                    [this, txn_id](const mtx::responses::EventId &event_id,
-                                                   mtx::http::RequestErr err) {
+                                    [this, txn_id, e](const mtx::responses::EventId &event_id,
+                                                      mtx::http::RequestErr err) {
                                             if (err) {
                                                     const int status_code =
                                                       static_cast(err->status_code);
@@ -108,7 +108,21 @@ EventStore::EventStore(std::string room_id, QObject *)
                                                     emit messageFailed(txn_id);
                                                     return;
                                             }
+
                                             emit messageSent(txn_id, event_id.event_id.to_string());
+                                            if constexpr (mtx::events::message_content_to_type<
+                                                            decltype(e.content)> ==
+                                                          mtx::events::EventType::RoomEncrypted) {
+                                                    auto event =
+                                                      decryptEvent({room_id_, e.event_id}, e);
+                                                    if (auto dec =
+                                                          std::get_if>(event)) {
+                                                            emit updateFlowEventId(
+                                                              event_id.event_id.to_string());
+                                                    }
+                                            }
                                     });
                   },
                   event->data);
@@ -318,12 +332,12 @@ EventStore::reactions(const std::string &event_id)
 
                 if (auto reaction = std::get_if>(
                       related_event)) {
-                        auto &agg = aggregation[reaction->content.relates_to.key];
+                        auto &agg = aggregation[reaction->content.relates_to.key.value()];
 
                         if (agg.count == 0) {
                                 Reaction temp{};
                                 temp.key_ =
-                                  QString::fromStdString(reaction->content.relates_to.key);
+                                  QString::fromStdString(reaction->content.relates_to.key.value());
                                 reactions.push_back(temp);
                         }
 
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 28d46e9..55a66f4 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -100,6 +100,7 @@ signals:
         void messageFailed(std::string txn_id);
         void startDMVerification(
           mtx::events::RoomEvent &msg);
+        void updateFlowEventId(std::string event_id);
 
 public slots:
         void addPending(mtx::events::collections::TimelineEvents event);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 809fe38..dc5eb8c 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -22,8 +22,6 @@
 #include "Utils.h"
 #include "dialogs/RawMessage.h"
 
-#include 
-
 Q_DECLARE_METATYPE(QModelIndex)
 
 namespace std {
@@ -237,6 +235,9 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 [this](mtx::events::RoomEvent msg) {
                         ChatPage::instance()->recievedRoomDeviceVerificationRequest(msg, this);
                 });
+        connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
+                this->updateFlowEventId(event_id);
+        });
 }
 
 QHash
@@ -814,7 +815,6 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg)
 
         json doc = {
           {"type", to_string(msg.type)}, {"content", json(msg.content)}, {"room_id", room_id}};
-        std::cout << doc.dump(2) << std::endl;
 
         try {
                 // Check if we have already an outbound megolm session then we can use.
@@ -1095,7 +1095,6 @@ struct SendMessageVisitor
 
         void operator()(const mtx::events::RoomEvent &msg)
         {
-                emit model_->updateFlowEventId(msg.event_id);
                 model_->sendEncryptedMessage(msg);
         }
         void operator()(const mtx::events::RoomEvent &msg)
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index c16e09d..fb4a094 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -194,13 +194,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           [this](const mtx::events::RoomEvent &message,
                  TimelineModel *model) {
                   if (!(this->dvList->exist(QString::fromStdString(message.event_id)))) {
-                          auto flow =
-                            new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::RoomMsg);
+                          auto flow = new DeviceVerificationFlow(
+                            this, DeviceVerificationFlow::Type::RoomMsg, model);
                           if (std::find(message.content.methods.begin(),
                                         message.content.methods.end(),
                                         mtx::events::msg::VerificationMethods::SASv1) !=
                               message.content.methods.end()) {
-                                  flow->setModel(model);
                                   flow->setEventId(message.event_id);
                                   emit newDeviceVerificationRequest(
                                     std::move(flow),
@@ -241,42 +240,48 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           &ChatPage::recievedDeviceVerificationStart,
           this,
           [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) {
-                  if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) {
-                          auto flow            = new DeviceVerificationFlow(this);
-                          flow->canonical_json = nlohmann::json(msg);
-                          if ((std::find(msg.key_agreement_protocols.begin(),
-                                         msg.key_agreement_protocols.end(),
-                                         "curve25519-hkdf-sha256") !=
-                               msg.key_agreement_protocols.end()) &&
-                              (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") !=
-                               msg.hashes.end()) &&
-                              (std::find(msg.message_authentication_codes.begin(),
-                                         msg.message_authentication_codes.end(),
-                                         "hmac-sha256") !=
-                               msg.message_authentication_codes.end())) {
-                                  if (std::find(msg.short_authentication_string.begin(),
-                                                msg.short_authentication_string.end(),
-                                                mtx::events::msg::SASMethods::Emoji) !=
-                                      msg.short_authentication_string.end()) {
-                                          flow->setMethod(DeviceVerificationFlow::Method::Emoji);
-                                  } else if (std::find(msg.short_authentication_string.begin(),
+                  if (msg.transaction_id.has_value()) {
+                          if (!(this->dvList->exist(
+                                QString::fromStdString(msg.transaction_id.value())))) {
+                                  auto flow            = new DeviceVerificationFlow(this);
+                                  flow->canonical_json = nlohmann::json(msg);
+                                  if ((std::find(msg.key_agreement_protocols.begin(),
+                                                 msg.key_agreement_protocols.end(),
+                                                 "curve25519-hkdf-sha256") !=
+                                       msg.key_agreement_protocols.end()) &&
+                                      (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") !=
+                                       msg.hashes.end()) &&
+                                      (std::find(msg.message_authentication_codes.begin(),
+                                                 msg.message_authentication_codes.end(),
+                                                 "hmac-sha256") !=
+                                       msg.message_authentication_codes.end())) {
+                                          if (std::find(msg.short_authentication_string.begin(),
+                                                        msg.short_authentication_string.end(),
+                                                        mtx::events::msg::SASMethods::Emoji) !=
+                                              msg.short_authentication_string.end()) {
+                                                  flow->setMethod(
+                                                    DeviceVerificationFlow::Method::Emoji);
+                                          } else if (std::find(
+                                                       msg.short_authentication_string.begin(),
                                                        msg.short_authentication_string.end(),
                                                        mtx::events::msg::SASMethods::Decimal) !=
-                                             msg.short_authentication_string.end()) {
-                                          flow->setMethod(DeviceVerificationFlow::Method::Decimal);
+                                                     msg.short_authentication_string.end()) {
+                                                  flow->setMethod(
+                                                    DeviceVerificationFlow::Method::Decimal);
+                                          } else {
+                                                  flow->cancelVerification(
+                                                    DeviceVerificationFlow::Error::UnknownMethod);
+                                                  return;
+                                          }
+                                          emit newDeviceVerificationRequest(
+                                            std::move(flow),
+                                            QString::fromStdString(msg.transaction_id.value()),
+                                            QString::fromStdString(sender),
+                                            QString::fromStdString(msg.from_device));
                                   } else {
                                           flow->cancelVerification(
                                             DeviceVerificationFlow::Error::UnknownMethod);
-                                          return;
                                   }
-                                  emit newDeviceVerificationRequest(
-                                    std::move(flow),
-                                    QString::fromStdString(msg.transaction_id.value()),
-                                    QString::fromStdString(sender),
-                                    QString::fromStdString(msg.from_device));
-                          } else {
-                                  flow->cancelVerification(
-                                    DeviceVerificationFlow::Error::UnknownMethod);
                           }
                   }
           });
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 1eaa9d2..87eae00 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -211,8 +211,9 @@ UserProfile::createFlow(bool isVerifyUser)
                                           << std::endl;
                                         if (this->roomid_.toStdString() == room_id) {
                                                 auto newflow = new DeviceVerificationFlow(
-                                                  this, DeviceVerificationFlow::Type::RoomMsg);
-                                                newflow->setModel(this->model);
+                                                  this,
+                                                  DeviceVerificationFlow::Type::RoomMsg,
+                                                  this->model);
                                                 return (std::move(newflow));
                                         } else {
                                                 std::cout << "FOUND A ENCRYPTED ROOM BUT CURRENTLY "

From 1d299951b61390c381013ca0503fb09df548c6ec Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Mon, 24 Aug 2020 13:56:50 +0530
Subject: [PATCH 32/70] Cache Fix

---
 src/Cache.cpp            |  46 +++++-----
 src/CacheCryptoStructs.h |   5 +-
 src/Cache_p.h            |   2 +
 src/ChatPage.cpp         |  35 ++++++++
 src/ChatPage.h           |   3 +
 src/ui/UserProfile.cpp   | 177 ++++++++++++++++++++++++---------------
 6 files changed, 181 insertions(+), 87 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 8cee345..cff0029 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -111,6 +111,24 @@ Cache::Cache(const QString &userId, QObject *parent)
   , localUserId_{userId}
 {
         setup();
+        connect(this,
+                &Cache::updateUserCacheFlag,
+                this,
+                [this](const std::string &user_id) {
+                        std::optional cache_ = getUserCache(user_id);
+                        if (cache_.has_value()) {
+                                cache_.value().isUpdated = false;
+                                setUserCache(user_id, cache_.value());
+                        } else {
+                                setUserCache(user_id, UserCache{});
+                        }
+                },
+                Qt::QueuedConnection);
+        connect(this,
+                &Cache::deleteLeftUsers,
+                this,
+                [this](const std::string &user_id) { deleteUserCache(user_id); },
+                Qt::QueuedConnection);
 }
 
 void
@@ -1011,7 +1029,7 @@ Cache::saveState(const mtx::responses::Sync &res)
 
         savePresence(txn, res.presence);
 
-        // updateUserCache(res.device_lists);
+        updateUserCache(res.device_lists);
 
         removeLeftRooms(txn, res.rooms.leave);
 
@@ -2889,13 +2907,15 @@ Cache::statusMessage(const std::string &user_id)
 void
 to_json(json &j, const UserCache &info)
 {
-        j["keys"] = info.keys;
+        j["keys"]      = info.keys;
+        j["isUpdated"] = info.isUpdated;
 }
 
 void
 from_json(const json &j, UserCache &info)
 {
-        info.keys = j.at("keys").get();
+        info.keys      = j.at("keys").get();
+        info.isUpdated = j.at("isUpdated").get();
 }
 
 std::optional
@@ -2935,26 +2955,12 @@ Cache::setUserCache(const std::string &user_id, const UserCache &body)
 void
 Cache::updateUserCache(const mtx::responses::DeviceLists body)
 {
-        for (auto user_id : body.changed) {
-                mtx::requests::QueryKeys req;
-                req.device_keys[user_id] = {};
-
-                http::client()->query_keys(
-                  req,
-                  [user_id, this](const mtx::responses::QueryKeys res, mtx::http::RequestErr err) {
-                          if (err) {
-                                  nhlog::net()->warn("failed to query device keys: {},{}",
-                                                     err->matrix_error.errcode,
-                                                     static_cast(err->status_code));
-                                  return;
-                          }
-
-                          setUserCache(user_id, UserCache{std::move(res)});
-                  });
+        for (std::string user_id : body.changed) {
+                emit updateUserCacheFlag(user_id);
         }
 
         for (std::string user_id : body.left) {
-                deleteUserCache(user_id);
+                emit deleteLeftUsers(user_id);
         }
 }
 
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index ba746f5..1dde21c 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -71,9 +71,12 @@ struct UserCache
 {
         //! map of public key key_ids and their public_key
         mtx::responses::QueryKeys keys;
+        //! if the current cache is updated or not
+        bool isUpdated = false;
 
-        UserCache(mtx::responses::QueryKeys res)
+        UserCache(mtx::responses::QueryKeys res, bool isUpdated_ = false)
           : keys(res)
+          , isUpdated(isUpdated_)
         {}
         UserCache() {}
 };
diff --git a/src/Cache_p.h b/src/Cache_p.h
index f75b0f4..174090a 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -269,6 +269,8 @@ signals:
         void newReadReceipts(const QString &room_id, const std::vector &event_ids);
         void roomReadStatus(const std::map &status);
         void removeNotification(const QString &room_id, const QString &event_id);
+        void updateUserCacheFlag(const std::string &user_id);
+        void deleteLeftUsers(const std::string &user_id);
 
 private:
         //! Save an invited room.
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index b97b6b3..f8cb31a 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -52,6 +52,8 @@
 
 #include "blurhash.hpp"
 
+#include  // only for debugging
+
 // TODO: Needs to be updated with an actual secret.
 static const std::string STORAGE_SECRET_KEY("secret");
 
@@ -1446,3 +1448,36 @@ ChatPage::initiateLogout()
 
         emit showOverlayProgressBar();
 }
+
+void
+ChatPage::query_keys(
+  const mtx::requests::QueryKeys &req,
+  std::function cb)
+{
+        std::string user_id = req.device_keys.begin()->first;
+        auto cache_         = cache::getUserCache(user_id);
+
+        if (cache_.has_value()) {
+                if (cache_.value().isUpdated) {
+                        cb(cache_.value().keys, {});
+                } else {
+                        http::client()->query_keys(
+                          req,
+                          [cb, user_id](const mtx::responses::QueryKeys &res,
+                                        mtx::http::RequestErr err) {
+                                  if (err) {
+                                          nhlog::net()->warn("failed to query device keys: {},{}",
+                                                             err->matrix_error.errcode,
+                                                             static_cast(err->status_code));
+                                          return;
+                                  }
+                                  std::cout << "Over here " << user_id << std::endl;
+                                  cache::setUserCache(std::move(user_id),
+                                                      std::move(UserCache{res, true}));
+                                  cb(res, err);
+                          });
+                }
+        } else {
+                http::client()->query_keys(req, cb);
+        }
+}
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 72adfe1..1080134 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -89,6 +89,9 @@ public:
         //! Show the room/group list (if it was visible).
         void showSideBars();
         void initiateLogout();
+        void query_keys(
+          const mtx::requests::QueryKeys &req,
+          std::function cb);
         void focusMessageInput();
 
         QString status() const;
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 87eae00..2426fe6 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -85,79 +85,124 @@ UserProfile::getUserStatus()
 }
 
 void
-UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
-                         mtx::http::RequestErr err,
-                         std::string user_id)
+UserProfile::fetchDeviceList(const QString &userID)
 {
-        if (err) {
-                nhlog::net()->warn("failed to query device keys: {},{}",
-                                   err->matrix_error.errcode,
-                                   static_cast(err->status_code));
-                return;
-        }
+        auto localUser = utils::localUser();
 
-        if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) {
-                nhlog::net()->warn("no devices retrieved {}", user_id);
-                return;
-        }
+        mtx::requests::QueryKeys req;
+        req.device_keys[userID.toStdString()] = {};
+        ChatPage::instance()->query_keys(
+          req,
+          [user_id = userID.toStdString(), local_user_id = localUser.toStdString(), this](
+            const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::net()->warn("failed to query device keys: {},{}",
+                                             err->matrix_error.errcode,
+                                             static_cast(err->status_code));
+                          return;
+                  }
 
-        auto devices = res.device_keys.at(user_id);
-        std::vector deviceInfo;
-        auto device_verified = cache::getVerifiedCache(user_id);
-
-        for (const auto &d : devices) {
-                auto device = d.second;
-
-                // TODO: Verify signatures and ignore those that don't pass.
-                verification::Status verified = verification::Status::UNVERIFIED;
-                isUserVerified                = device_verified->is_user_verified;
-                if (device_verified.has_value()) {
-                        if (std::find(device_verified->cross_verified.begin(),
-                                      device_verified->cross_verified.end(),
-                                      d.first) != device_verified->cross_verified.end())
-                                verified = verification::Status::VERIFIED;
-                        if (std::find(device_verified->device_verified.begin(),
-                                      device_verified->device_verified.end(),
-                                      d.first) != device_verified->device_verified.end())
-                                verified = verification::Status::VERIFIED;
-                        if (std::find(device_verified->device_blocked.begin(),
-                                      device_verified->device_blocked.end(),
-                                      d.first) != device_verified->device_blocked.end())
-                                verified = verification::Status::BLOCKED;
-                }
+                  if (res.device_keys.empty() ||
+                      (res.device_keys.find(user_id) == res.device_keys.end())) {
+                          nhlog::net()->warn("no devices retrieved {}", user_id);
+                          return;
+                  }
 
-                deviceInfo.push_back(
-                  {QString::fromStdString(d.first),
-                   QString::fromStdString(device.unsigned_info.device_display_name),
-                   verified});
-        }
+                  auto devices = res.device_keys.at(user_id);
+                  std::vector deviceInfo;
+                  auto device_verified = cache::getVerifiedCache(user_id);
 
-        std::sort(
-          deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) {
-                  return a.device_id > b.device_id;
-          });
+                  for (const auto &d : devices) {
+                          auto device = d.second;
 
-        this->deviceList_.queueReset(std::move(deviceInfo));
-}
+                          // TODO: Verify signatures and ignore those that don't pass.
+                          verification::Status verified = verification::Status::UNVERIFIED;
+                          isUserVerified                = device_verified->is_user_verified;
+                          if (device_verified.has_value()) {
+                                  if (std::find(device_verified->cross_verified.begin(),
+                                                device_verified->cross_verified.end(),
+                                                d.first) != device_verified->cross_verified.end())
+                                          verified = verification::Status::VERIFIED;
+                                  if (std::find(device_verified->device_verified.begin(),
+                                                device_verified->device_verified.end(),
+                                                d.first) != device_verified->device_verified.end())
+                                          verified = verification::Status::VERIFIED;
+                                  if (std::find(device_verified->device_blocked.begin(),
+                                                device_verified->device_blocked.end(),
+                                                d.first) != device_verified->device_blocked.end())
+                                          verified = verification::Status::BLOCKED;
+                          }
 
-void
-UserProfile::fetchDeviceList(const QString &userID)
-{
-        auto localUser  = utils::localUser();
-        auto user_cache = cache::getUserCache(userID.toStdString());
-
-        if (user_cache.has_value()) {
-                this->callback_fn(user_cache->keys, {}, userID.toStdString());
-        } else {
-                mtx::requests::QueryKeys req;
-                req.device_keys[userID.toStdString()] = {};
-                http::client()->query_keys(
-                  req,
-                  [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
-                                                         mtx::http::RequestErr err) {
-                          this->callback_fn(res, err, user_id);
-                  });
-        }
+                          deviceInfo.push_back(
+                            {QString::fromStdString(d.first),
+                             QString::fromStdString(device.unsigned_info.device_display_name),
+                             verified});
+                  }
+
+                  // Finding if the User is Verified or not based on the Signatures
+                  mtx::requests::QueryKeys req;
+                  req.device_keys[local_user_id] = {};
+
+                  ChatPage::instance()->query_keys(
+                    req,
+                    [&local_user_id, &user_id, other_res = res, this](
+                      const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
+                            using namespace mtx;
+
+                            if (err) {
+                                    nhlog::net()->warn("failed to query device keys: {},{}",
+                                                       err->matrix_error.errcode,
+                                                       static_cast(err->status_code));
+                                    return;
+                            }
+
+                            std::optional lmk, lsk, luk, mk, sk, uk;
+
+                            if (res.master_keys.find(local_user_id) != res.master_keys.end())
+                                    lmk = res.master_keys.at(local_user_id);
+                            if (res.user_signing_keys.find(local_user_id) !=
+                                res.user_signing_keys.end())
+                                    luk = res.user_signing_keys.at(local_user_id);
+                            if (res.self_signing_keys.find(local_user_id) !=
+                                res.self_signing_keys.end())
+                                    lsk = res.self_signing_keys.at(local_user_id);
+                            if (other_res.master_keys.find(user_id) != other_res.master_keys.end())
+                                    mk = other_res.master_keys.at(user_id);
+                            if (other_res.user_signing_keys.find(user_id) !=
+                                other_res.user_signing_keys.end())
+                                    uk = other_res.user_signing_keys.at(user_id);
+                            if (other_res.self_signing_keys.find(user_id) !=
+                                other_res.self_signing_keys.end())
+                                    sk = other_res.self_signing_keys.at(user_id);
+
+                            // First checking if the user is verified
+                            if (lmk.has_value() && luk.has_value()) {
+                                    bool is_user_verified = false;
+                                    for (auto sign_key : lmk.value().keys) {
+                                            if (!luk.value().signatures.empty()) {
+                                                    for (auto signature :
+                                                         luk.value().signatures.at(local_user_id)) {
+                                                            is_user_verified =
+                                                              is_user_verified ||
+                                                              (olm::client()->ed25519_verify_sig(
+                                                                sign_key.second,
+                                                                json(luk.value()),
+                                                                signature.second));
+                                                    }
+                                            }
+                                    }
+                                    std::cout << (isUserVerified ? "Yes" : "No") << std::endl;
+                            }
+                    });
+
+                  std::sort(deviceInfo.begin(),
+                            deviceInfo.end(),
+                            [](const DeviceInfo &a, const DeviceInfo &b) {
+                                    return a.device_id > b.device_id;
+                            });
+
+                  this->deviceList_.queueReset(std::move(deviceInfo));
+          });
 }
 
 void

From 19cfd08a554e20862c1187148982d542e311411d Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Tue, 25 Aug 2020 15:41:27 +0530
Subject: [PATCH 33/70] Verify signatures and find trusted devices

---
 src/ui/UserProfile.cpp | 181 ++++++++++++++++++++++++++---------------
 1 file changed, 117 insertions(+), 64 deletions(-)

diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 2426fe6..48a3ffa 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -93,8 +93,8 @@ UserProfile::fetchDeviceList(const QString &userID)
         req.device_keys[userID.toStdString()] = {};
         ChatPage::instance()->query_keys(
           req,
-          [user_id = userID.toStdString(), local_user_id = localUser.toStdString(), this](
-            const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
+          [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
+                                                 mtx::http::RequestErr err) {
                   if (err) {
                           nhlog::net()->warn("failed to query device keys: {},{}",
                                              err->matrix_error.errcode,
@@ -108,46 +108,16 @@ UserProfile::fetchDeviceList(const QString &userID)
                           return;
                   }
 
-                  auto devices = res.device_keys.at(user_id);
-                  std::vector deviceInfo;
-                  auto device_verified = cache::getVerifiedCache(user_id);
-
-                  for (const auto &d : devices) {
-                          auto device = d.second;
-
-                          // TODO: Verify signatures and ignore those that don't pass.
-                          verification::Status verified = verification::Status::UNVERIFIED;
-                          isUserVerified                = device_verified->is_user_verified;
-                          if (device_verified.has_value()) {
-                                  if (std::find(device_verified->cross_verified.begin(),
-                                                device_verified->cross_verified.end(),
-                                                d.first) != device_verified->cross_verified.end())
-                                          verified = verification::Status::VERIFIED;
-                                  if (std::find(device_verified->device_verified.begin(),
-                                                device_verified->device_verified.end(),
-                                                d.first) != device_verified->device_verified.end())
-                                          verified = verification::Status::VERIFIED;
-                                  if (std::find(device_verified->device_blocked.begin(),
-                                                device_verified->device_blocked.end(),
-                                                d.first) != device_verified->device_blocked.end())
-                                          verified = verification::Status::BLOCKED;
-                          }
-
-                          deviceInfo.push_back(
-                            {QString::fromStdString(d.first),
-                             QString::fromStdString(device.unsigned_info.device_display_name),
-                             verified});
-                  }
-
                   // Finding if the User is Verified or not based on the Signatures
                   mtx::requests::QueryKeys req;
-                  req.device_keys[local_user_id] = {};
+                  req.device_keys[utils::localUser().toStdString()] = {};
 
                   ChatPage::instance()->query_keys(
                     req,
-                    [&local_user_id, &user_id, other_res = res, this](
-                      const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
+                    [user_id, other_res = res, this](const mtx::responses::QueryKeys &res,
+                                                     mtx::http::RequestErr err) {
                             using namespace mtx;
+                            std::string local_user_id = utils::localUser().toStdString();
 
                             if (err) {
                                     nhlog::net()->warn("failed to query device keys: {},{}",
@@ -156,52 +126,135 @@ UserProfile::fetchDeviceList(const QString &userID)
                                     return;
                             }
 
+                            if (res.device_keys.empty() ||
+                                (res.device_keys.find(local_user_id) == res.device_keys.end())) {
+                                    nhlog::net()->warn("no devices retrieved {}", user_id);
+                                    return;
+                            }
+
+                            std::vector deviceInfo;
+                            auto devices         = other_res.device_keys.at(user_id);
+                            auto device_verified = cache::getVerifiedCache(user_id);
+
+                            if (device_verified.has_value()) {
+                                    isUserVerified = device_verified.value().is_user_verified;
+                            }
+
                             std::optional lmk, lsk, luk, mk, sk, uk;
 
-                            if (res.master_keys.find(local_user_id) != res.master_keys.end())
+                            if (!res.master_keys.empty())
                                     lmk = res.master_keys.at(local_user_id);
-                            if (res.user_signing_keys.find(local_user_id) !=
-                                res.user_signing_keys.end())
+                            if (!res.user_signing_keys.empty())
                                     luk = res.user_signing_keys.at(local_user_id);
-                            if (res.self_signing_keys.find(local_user_id) !=
-                                res.self_signing_keys.end())
+                            if (!res.self_signing_keys.empty())
                                     lsk = res.self_signing_keys.at(local_user_id);
-                            if (other_res.master_keys.find(user_id) != other_res.master_keys.end())
+                            if (!other_res.master_keys.empty())
                                     mk = other_res.master_keys.at(user_id);
-                            if (other_res.user_signing_keys.find(user_id) !=
-                                other_res.user_signing_keys.end())
+                            if (!other_res.user_signing_keys.empty())
                                     uk = other_res.user_signing_keys.at(user_id);
-                            if (other_res.self_signing_keys.find(user_id) !=
-                                other_res.self_signing_keys.end())
+                            if (!other_res.self_signing_keys.empty())
                                     sk = other_res.self_signing_keys.at(user_id);
 
                             // First checking if the user is verified
-                            if (lmk.has_value() && luk.has_value()) {
-                                    bool is_user_verified = false;
-                                    for (auto sign_key : lmk.value().keys) {
-                                            if (!luk.value().signatures.empty()) {
-                                                    for (auto signature :
-                                                         luk.value().signatures.at(local_user_id)) {
-                                                            is_user_verified =
-                                                              is_user_verified ||
+                            if (luk.has_value() && mk.has_value()) {
+                                    // iterating through the public key of local user_signing keys
+                                    for (auto sign_key : luk.value().keys) {
+                                            // checking if the signatures are empty as "at" could
+                                            // cause exceptions
+                                            if (!mk.value().signatures.empty()) {
+                                                    auto signs =
+                                                      mk.value().signatures.at(local_user_id);
+                                                    try {
+                                                            isUserVerified =
+                                                              isUserVerified ||
                                                               (olm::client()->ed25519_verify_sig(
                                                                 sign_key.second,
-                                                                json(luk.value()),
-                                                                signature.second));
+                                                                json(mk.value()),
+                                                                signs.at(sign_key.first)));
+                                                    } catch (std::out_of_range) {
+                                                            isUserVerified =
+                                                              isUserVerified || false;
                                                     }
                                             }
                                     }
-                                    std::cout << (isUserVerified ? "Yes" : "No") << std::endl;
                             }
-                    });
 
-                  std::sort(deviceInfo.begin(),
-                            deviceInfo.end(),
-                            [](const DeviceInfo &a, const DeviceInfo &b) {
-                                    return a.device_id > b.device_id;
-                            });
+                            for (const auto &d : devices) {
+                                    auto device = d.second;
+                                    verification::Status verified =
+                                      verification::Status::UNVERIFIED;
+
+                                    if (device_verified.has_value()) {
+                                            if (std::find(device_verified->cross_verified.begin(),
+                                                          device_verified->cross_verified.end(),
+                                                          d.first) !=
+                                                device_verified->cross_verified.end())
+                                                    verified = verification::Status::VERIFIED;
+                                            if (std::find(device_verified->device_verified.begin(),
+                                                          device_verified->device_verified.end(),
+                                                          d.first) !=
+                                                device_verified->device_verified.end())
+                                                    verified = verification::Status::VERIFIED;
+                                            if (std::find(device_verified->device_blocked.begin(),
+                                                          device_verified->device_blocked.end(),
+                                                          d.first) !=
+                                                device_verified->device_blocked.end())
+                                                    verified = verification::Status::BLOCKED;
+                                    } else if (isUserVerified) {
+                                            device_verified = DeviceVerifiedCache{};
+                                    }
 
-                  this->deviceList_.queueReset(std::move(deviceInfo));
+                                    // won't check for already verified devices
+                                    if (verified != verification::Status::VERIFIED &&
+                                        isUserVerified) {
+                                            if ((sk.has_value()) && (!device.signatures.empty())) {
+                                                    for (auto sign_key : sk.value().keys) {
+                                                            auto signs =
+                                                              device.signatures.at(user_id);
+                                                            try {
+                                                                    if (olm::client()
+                                                                          ->ed25519_verify_sig(
+                                                                            sign_key.second,
+                                                                            json(device),
+                                                                            signs.at(
+                                                                              sign_key.first))) {
+                                                                            verified =
+                                                                              verification::Status::
+                                                                                VERIFIED;
+                                                                            device_verified.value()
+                                                                              .cross_verified
+                                                                              .push_back(d.first);
+                                                                    }
+                                                            } catch (std::out_of_range) {
+                                                            }
+                                                    }
+                                            }
+                                    }
+
+                                    if (device_verified.has_value()) {
+                                            device_verified.value().is_user_verified =
+                                              isUserVerified;
+                                            cache::setVerifiedCache(user_id,
+                                                                    device_verified.value());
+                                    }
+
+                                    deviceInfo.push_back(
+                                      {QString::fromStdString(d.first),
+                                       QString::fromStdString(
+                                         device.unsigned_info.device_display_name),
+                                       verified});
+                            }
+
+                            std::cout << (isUserVerified ? "Yes" : "No") << std::endl;
+
+                            std::sort(deviceInfo.begin(),
+                                      deviceInfo.end(),
+                                      [](const DeviceInfo &a, const DeviceInfo &b) {
+                                              return a.device_id > b.device_id;
+                                      });
+
+                            this->deviceList_.queueReset(std::move(deviceInfo));
+                    });
           });
 }
 

From 0d1dd29b19a3f4459b036bd63f03c518e000d71f Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sat, 29 Aug 2020 13:37:51 +0530
Subject: [PATCH 34/70] Small Fixes

---
 .../device-verification/DeviceVerification.qml   |  1 -
 src/ChatPage.cpp                                 |  3 ---
 src/DeviceVerificationFlow.cpp                   | 16 +++++++++++++++-
 src/timeline/EventStore.cpp                      |  7 +++++++
 src/ui/UserProfile.cpp                           |  9 +++++----
 5 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index f40a7b8..94cb1e3 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -579,7 +579,6 @@ ApplicationWindow {
 						onClicked: {
 							dialog.close();
 							deviceVerificationList.remove(flow.tranId);
-							delete flow;
 						}
 					}
 				}
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index f8cb31a..909d81e 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -52,8 +52,6 @@
 
 #include "blurhash.hpp"
 
-#include  // only for debugging
-
 // TODO: Needs to be updated with an actual secret.
 static const std::string STORAGE_SECRET_KEY("secret");
 
@@ -1471,7 +1469,6 @@ ChatPage::query_keys(
                                                              static_cast(err->status_code));
                                           return;
                                   }
-                                  std::cout << "Over here " << user_id << std::endl;
                                   cache::setUserCache(std::move(user_id),
                                                       std::move(UserCache{res, true}));
                                   cb(res, err);
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 8c23088..dd82842 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -7,6 +7,7 @@
 
 #include 
 #include 
+#include 
 
 static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
 
@@ -75,7 +76,14 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                                     DeviceVerificationFlow::Error::UnknownMethod);
                                   return;
                           }
-                          this->canonical_json = nlohmann::json(msg);
+                          if (!sender)
+                                  this->canonical_json = nlohmann::json(msg);
+                          else {
+                                  if (utils::localUser().toStdString() <
+                                      this->toClient.to_string()) {
+                                          this->canonical_json = nlohmann::json(msg);
+                                  }
+                          }
                           this->acceptVerificationRequest();
                   } else {
                           this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
@@ -124,6 +132,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                                 if (msg.relates_to.value().event_id != this->relation.event_id)
                                         return;
                         }
+                        this->deleteLater();
                         emit verificationCanceled();
                 });
 
@@ -226,6 +235,11 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                 &ChatPage::recievedDeviceVerificationReady,
                 this,
                 [this](const mtx::events::msg::KeyVerificationReady &msg) {
+                        if (!sender) {
+                                this->deleteLater();
+                                emit verificationCanceled();
+                                return;
+                        }
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index b210e15..6326e98 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -298,6 +298,13 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                                           msg->content, msg->sender);
                                 }
                         }
+                        // only the key.verification.ready sent by localuser's other device is of
+                        // significance as it is used for detecting accepted request
+                        if (auto msg = std::get_if<
+                              mtx::events::RoomEvent>(
+                              event)) {
+                                ChatPage::instance()->recievedDeviceVerificationReady(msg->content);
+                        }
                 }
         }
 
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 48a3ffa..59be346 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -161,16 +161,17 @@ UserProfile::fetchDeviceList(const QString &userID)
                                     for (auto sign_key : luk.value().keys) {
                                             // checking if the signatures are empty as "at" could
                                             // cause exceptions
-                                            if (!mk.value().signatures.empty()) {
-                                                    auto signs =
-                                                      mk.value().signatures.at(local_user_id);
+                                            auto signs = mk->signatures;
+                                            if (!signs.empty() &&
+                                                signs.find(local_user_id) != signs.end()) {
+                                                    auto sign = signs.at(local_user_id);
                                                     try {
                                                             isUserVerified =
                                                               isUserVerified ||
                                                               (olm::client()->ed25519_verify_sig(
                                                                 sign_key.second,
                                                                 json(mk.value()),
-                                                                signs.at(sign_key.first)));
+                                                                sign.at(sign_key.first)));
                                                     } catch (std::out_of_range) {
                                                             isUserVerified =
                                                               isUserVerified || false;

From 9a76db85d5d18fd51b8ec2841553fea708e6014a Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sun, 30 Aug 2020 16:32:28 +0530
Subject: [PATCH 35/70] Change ReactionRealtesTo to RelatesTo

---
 src/DeviceVerificationFlow.h | 2 +-
 src/Olm.cpp                  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 4c3e517..6b2ab81 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -127,5 +127,5 @@ private:
         std::optional room_id;
         std::optional event_id;
         TimelineModel *model_;
-        mtx::common::ReactionRelatesTo relation;
+        mtx::common::RelatesTo relation;
 };
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 48439fa..9e1a4ed 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -211,7 +211,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
 
         // relations shouldn't be encrypted...
         mtx::common::ReplyRelatesTo relation;
-        mtx::common::ReactionRelatesTo r_relation;
+        mtx::common::RelatesTo r_relation;
 
         if (body["content"].contains("m.relates_to") &&
             body["content"]["m.relates_to"].contains("m.in_reply_to")) {

From 3396a7a7967de5500372499107695d484b05b50b Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sun, 30 Aug 2020 17:05:54 +0530
Subject: [PATCH 36/70] Change the tag for mtxclient

---
 CMakeLists.txt                   | 2 +-
 io.github.NhekoReborn.Nheko.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2aff991..de617dc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -337,7 +337,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        fa6e36dbcd922c1920873b3fcdfe0a9d283f082e
+		GIT_TAG        0665c8baf4af0ce192adb8ca97761b63b681d569
 		)
 	FetchContent_MakeAvailable(MatrixClient)
 else()
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index 8cdd7b9..c71a577 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -146,7 +146,7 @@
       "name": "mtxclient",
       "sources": [
         {
-          "commit": "fa6e36dbcd922c1920873b3fcdfe0a9d283f082e",
+          "commit": "0665c8baf4af0ce192adb8ca97761b63b681d569",
           "type": "git",
           "url": "https://github.com/Nheko-Reborn/mtxclient.git"
         }

From f03a48eec569e7cae52c8c94f530d0540ea2c605 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Sun, 30 Aug 2020 22:36:53 +0530
Subject: [PATCH 37/70] fix

---
 src/DeviceVerificationFlow.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index d2df0bb..6b2ab81 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -1,4 +1,4 @@
-ith#pragma once
+#pragma once
 
 #include "Olm.h"
 

From e0981e17a1887f3e3068b35ce096474735bc733c Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sun, 30 Aug 2020 19:32:21 +0200
Subject: [PATCH 38/70] Fix reactions

---
 resources/qml/Reactions.qml         | 5 ++---
 resources/qml/TimelineRow.qml       | 1 -
 resources/qml/emoji/EmojiPicker.qml | 2 +-
 3 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml
index 9fc30f6..ec46f7e 100644
--- a/resources/qml/Reactions.qml
+++ b/resources/qml/Reactions.qml
@@ -14,7 +14,6 @@ Flow {
 	property real highlightLight: colors.highlight.hslLightness
 
 	property string eventId
-	property string roomId
 
 	anchors.left: parent.left
 	anchors.right: parent.right
@@ -35,7 +34,7 @@ Flow {
 			ToolTip.text: modelData.users
 
 			onClicked: {
-				console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + modelData.selfReactedEvent)
+				console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent)
 				TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key)
 			}
 
@@ -57,7 +56,7 @@ Flow {
 				Text {
 					anchors.baseline: reactionCounter.baseline
 					id: reactionText
-					text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…")
+					text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
 					font.family: Settings.emojiFont
 					color: reaction.hovered ? colors.highlight : colors.text
 					maximumLineCount: 1
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 2979908..c026d82 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -64,7 +64,6 @@ Item {
 			Reactions {
 				id: reactionRow
 				reactions: model.reactions
-				roomId: model.roomId
 				eventId: model.id
 			}
 		}
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index d2ca4da..cbb77be 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -104,7 +104,7 @@ Popup {
                 onClicked: {
                     console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id)
                     emojiPopup.close()
-                    TimelineManager.queueReactionMessage(emojiPopup.room_id, emojiPopup.event_id, model.unicode)
+                    TimelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode)
                 }
             }
 

From 10f09d4f432d8f582972a22ebb2d1436d1335eb9 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sun, 30 Aug 2020 19:33:10 +0200
Subject: [PATCH 39/70] Fix catch by value warning

---
 src/ui/UserProfile.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 59be346..08c3009 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -172,7 +172,7 @@ UserProfile::fetchDeviceList(const QString &userID)
                                                                 sign_key.second,
                                                                 json(mk.value()),
                                                                 sign.at(sign_key.first)));
-                                                    } catch (std::out_of_range) {
+                                                    } catch (std::out_of_range &) {
                                                             isUserVerified =
                                                               isUserVerified || false;
                                                     }
@@ -226,7 +226,7 @@ UserProfile::fetchDeviceList(const QString &userID)
                                                                               .cross_verified
                                                                               .push_back(d.first);
                                                                     }
-                                                            } catch (std::out_of_range) {
+                                                            } catch (std::out_of_range &) {
                                                             }
                                                     }
                                             }
@@ -326,4 +326,4 @@ UserProfile::createFlow(bool isVerifyUser)
                 std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl;
                 return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice));
         }
-}
\ No newline at end of file
+}

From 5358854de37fcf51c5d24f022b231da17f3b85c7 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Thu, 3 Sep 2020 09:29:32 +0530
Subject: [PATCH 40/70] Add support for Encrypted to-device verification
 messages

---
 src/DeviceVerificationFlow.cpp |  2 +-
 src/Olm.cpp                    | 45 ++++++++++++++++++++++++++++++++++
 src/timeline/TimelineModel.cpp |  2 +-
 3 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 00c9602..3786671 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -165,7 +165,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                         }
 
                         if (this->method == DeviceVerificationFlow::Method::Emoji) {
-                                std::cout<sasList = this->sas->generate_bytes_emoji(info);
                         } else if (this->method == DeviceVerificationFlow::Method::Decimal) {
                                 this->sasList = this->sas->generate_bytes_decimal(info);
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 74af61d..9e99780 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -138,6 +138,51 @@ handle_olm_message(const OlmMessage &msg)
 
                 auto payload = try_olm_decryption(msg.sender_key, cipher.second);
 
+                if (!payload.is_null()) {
+                        std::string msg_type = payload["type"];
+
+                        if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) {
+                                ChatPage::instance()->recievedDeviceVerificationAccept(
+                                  payload["content"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationRequest)) {
+                                ChatPage::instance()->recievedDeviceVerificationRequest(
+                                  payload["content"], payload["sender"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationCancel)) {
+                                ChatPage::instance()->recievedDeviceVerificationCancel(
+                                  payload["content"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationKey)) {
+                                ChatPage::instance()->recievedDeviceVerificationKey(
+                                  payload["content"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationMac)) {
+                                ChatPage::instance()->recievedDeviceVerificationMac(
+                                  payload["content"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationStart)) {
+                                ChatPage::instance()->recievedDeviceVerificationStart(
+                                  payload["content"], payload["sender"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationReady)) {
+                                ChatPage::instance()->recievedDeviceVerificationReady(
+                                  payload["content"]);
+                                return;
+                        } else if (msg_type ==
+                                   to_string(mtx::events::EventType::KeyVerificationDone)) {
+                                ChatPage::instance()->recievedDeviceVerificationDone(
+                                  payload["content"]);
+                                return;
+                        }
+                }
+
                 if (!payload.is_null()) {
                         nhlog::crypto()->debug("decrypted olm payload: {}", payload.dump(2));
                         create_inbound_megolm_session(msg.sender, msg.sender_key, payload);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index aea2645..8f0e470 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -879,7 +879,7 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::
         using namespace mtx::identifiers;
 
         json doc = {{"type", mtx::events::to_string(eventType)},
-                    {"content", msg.content},
+                    {"content", json(msg.content)},
                     {"room_id", room_id}};
 
         try {

From e8eeb480d51b6fc60c9807dd92195e9068582592 Mon Sep 17 00:00:00 2001
From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com>
Date: Fri, 4 Sep 2020 12:02:24 +0530
Subject: [PATCH 41/70] Fix Wrong Emojis Issue in Room Verification

---
 resources/qml/MatrixText.qml   |   2 +-
 src/Cache.cpp                  |  38 +++---
 src/ChatPage.cpp               |  11 +-
 src/DeviceVerificationFlow.cpp |  28 ++--
 src/DeviceVerificationFlow.h   |   2 +-
 src/timeline/EventStore.cpp    | 231 ++++++++++++++++++---------------
 src/timeline/TimelineModel.cpp |  33 ++---
 src/ui/UserProfile.h           |   2 +-
 8 files changed, 190 insertions(+), 157 deletions(-)

diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index bbbb80c..2921416 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -19,7 +19,7 @@ TextEdit {
 			TimelineManager.setHistoryView(match[1])
 			chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
 		}
-		else timelineManager.openLink(link)
+		else TimelineManager.openLink(link)
 	}
 	MouseArea
 	{
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 5302218..07d0181 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -139,24 +139,26 @@ Cache::Cache(const QString &userId, QObject *parent)
   , localUserId_{userId}
 {
         setup();
-        connect(this,
-                &Cache::updateUserCacheFlag,
-                this,
-                [this](const std::string &user_id) {
-                        std::optional cache_ = getUserCache(user_id);
-                        if (cache_.has_value()) {
-                                cache_.value().isUpdated = false;
-                                setUserCache(user_id, cache_.value());
-                        } else {
-                                setUserCache(user_id, UserCache{});
-                        }
-                },
-                Qt::QueuedConnection);
-        connect(this,
-                &Cache::deleteLeftUsers,
-                this,
-                [this](const std::string &user_id) { deleteUserCache(user_id); },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &Cache::updateUserCacheFlag,
+          this,
+          [this](const std::string &user_id) {
+                  std::optional cache_ = getUserCache(user_id);
+                  if (cache_.has_value()) {
+                          cache_.value().isUpdated = false;
+                          setUserCache(user_id, cache_.value());
+                  } else {
+                          setUserCache(user_id, UserCache{});
+                  }
+          },
+          Qt::QueuedConnection);
+        connect(
+          this,
+          &Cache::deleteLeftUsers,
+          this,
+          [this](const std::string &user_id) { deleteUserCache(user_id); },
+          Qt::QueuedConnection);
 }
 
 void
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 31ba38d..704543b 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -606,11 +606,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(
           this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
         connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
-        connect(this,
-                &ChatPage::tryDelayedSyncCb,
-                this,
-                [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &ChatPage::tryDelayedSyncCb,
+          this,
+          [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
+          Qt::QueuedConnection);
 
         connect(this,
                 &ChatPage::newSyncResponse,
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 3786671..ae054af 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -28,10 +28,10 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                 connect(this->model_,
                         &TimelineModel::updateFlowEventId,
                         this,
-                        [this](std::string event_id) {
+                        [this](std::string event_id_) {
                                 this->relation.rel_type = mtx::common::RelationType::Reference;
-                                this->relation.event_id = event_id;
-                                this->transaction_id    = event_id;
+                                this->relation.event_id = event_id_;
+                                this->transaction_id    = event_id_;
                         });
         }
 
@@ -60,7 +60,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                        msg.hashes.end()) &&
                       (std::find(msg.message_authentication_codes.begin(),
                                  msg.message_authentication_codes.end(),
-                                 "hmac-sha256") != msg.message_authentication_codes.end())) {
+                                 "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) {
                           if (std::find(msg.short_authentication_string.begin(),
                                         msg.short_authentication_string.end(),
                                         mtx::events::msg::SASMethods::Decimal) !=
@@ -236,11 +236,15 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                 &ChatPage::recievedDeviceVerificationReady,
                 this,
                 [this](const mtx::events::msg::KeyVerificationReady &msg) {
-                        if (!sender && msg.from_device != http::client()->device_id()) {
-                                this->deleteLater();
-                                emit verificationCanceled();
+                        if (!sender) {
+                                if (msg.from_device != http::client()->device_id()) {
+                                        this->deleteLater();
+                                        emit verificationCanceled();
+                                }
+
                                 return;
                         }
+
                         if (msg.transaction_id.has_value()) {
                                 if (msg.transaction_id.value() != this->transaction_id)
                                         return;
@@ -353,9 +357,9 @@ DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_)
 }
 
 void
-DeviceVerificationFlow::setType(Type type)
+DeviceVerificationFlow::setType(Type type_)
 {
-        this->type = type;
+        this->type = type_;
 }
 
 void
@@ -367,11 +371,11 @@ DeviceVerificationFlow::setSender(bool sender_)
 }
 
 void
-DeviceVerificationFlow::setEventId(std::string event_id)
+DeviceVerificationFlow::setEventId(std::string event_id_)
 {
         this->relation.rel_type = mtx::common::RelationType::Reference;
-        this->relation.event_id = event_id;
-        this->transaction_id    = event_id;
+        this->relation.event_id = event_id_;
+        this->transaction_id    = event_id_;
 }
 
 //! accepts a verification
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 6b2ab81..c215098 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -10,7 +10,7 @@ class QTimer;
 
 using sas_ptr = std::unique_ptr;
 
-struct TimelineModel;
+class TimelineModel;
 
 class DeviceVerificationFlow : public QObject
 {
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index bfc16a0..e5eaefb 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -32,38 +32,40 @@ EventStore::EventStore(std::string room_id, QObject *)
                 this->last  = range->last;
         }
 
-        connect(this,
-                &EventStore::eventFetched,
-                this,
-                [this](std::string id,
-                       std::string relatedTo,
-                       mtx::events::collections::TimelineEvents timeline) {
-                        cache::client()->storeEvent(room_id_, id, {timeline});
-
-                        if (!relatedTo.empty()) {
-                                auto idx = idToIndex(relatedTo);
-                                if (idx)
-                                        emit dataChanged(*idx, *idx);
-                        }
-                },
-                Qt::QueuedConnection);
-
-        connect(this,
-                &EventStore::oldMessagesRetrieved,
-                this,
-                [this](const mtx::responses::Messages &res) {
-                        uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
-                        if (newFirst == first && !res.chunk.empty())
-                                fetchMore();
-                        else {
-                                emit beginInsertRows(toExternalIdx(newFirst),
-                                                     toExternalIdx(this->first - 1));
-                                this->first = newFirst;
-                                emit endInsertRows();
-                                emit fetchedMore();
-                        }
-                },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &EventStore::eventFetched,
+          this,
+          [this](std::string id,
+                 std::string relatedTo,
+                 mtx::events::collections::TimelineEvents timeline) {
+                  cache::client()->storeEvent(room_id_, id, {timeline});
+
+                  if (!relatedTo.empty()) {
+                          auto idx = idToIndex(relatedTo);
+                          if (idx)
+                                  emit dataChanged(*idx, *idx);
+                  }
+          },
+          Qt::QueuedConnection);
+
+        connect(
+          this,
+          &EventStore::oldMessagesRetrieved,
+          this,
+          [this](const mtx::responses::Messages &res) {
+                  uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
+                  if (newFirst == first)
+                          fetchMore();
+                  else {
+                          emit beginInsertRows(toExternalIdx(newFirst),
+                                               toExternalIdx(this->first - 1));
+                          this->first = newFirst;
+                          emit endInsertRows();
+                          emit fetchedMore();
+                  }
+          },
+          Qt::QueuedConnection);
 
         connect(this, &EventStore::processPending, this, [this]() {
                 if (!current_txn.empty()) {
@@ -128,46 +130,48 @@ EventStore::EventStore(std::string room_id, QObject *)
                   event->data);
         });
 
-        connect(this,
-                &EventStore::messageFailed,
-                this,
-                [this](std::string txn_id) {
-                        if (current_txn == txn_id) {
-                                current_txn_error_count++;
-                                if (current_txn_error_count > 10) {
-                                        nhlog::ui()->debug("failing txn id '{}'", txn_id);
-                                        cache::client()->removePendingStatus(room_id_, txn_id);
-                                        current_txn_error_count = 0;
-                                }
-                        }
-                        QTimer::singleShot(1000, this, [this]() {
-                                nhlog::ui()->debug("timeout");
-                                this->current_txn = "";
-                                emit processPending();
-                        });
-                },
-                Qt::QueuedConnection);
-
-        connect(this,
-                &EventStore::messageSent,
-                this,
-                [this](std::string txn_id, std::string event_id) {
-                        nhlog::ui()->debug("sent {}", txn_id);
-
-                        http::client()->read_event(
-                          room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
-                                  if (err) {
-                                          nhlog::net()->warn(
-                                            "failed to read_event ({}, {})", room_id_, event_id);
-                                  }
-                          });
-
-                        cache::client()->removePendingStatus(room_id_, txn_id);
-                        this->current_txn             = "";
-                        this->current_txn_error_count = 0;
-                        emit processPending();
-                },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &EventStore::messageFailed,
+          this,
+          [this](std::string txn_id) {
+                  if (current_txn == txn_id) {
+                          current_txn_error_count++;
+                          if (current_txn_error_count > 10) {
+                                  nhlog::ui()->debug("failing txn id '{}'", txn_id);
+                                  cache::client()->removePendingStatus(room_id_, txn_id);
+                                  current_txn_error_count = 0;
+                          }
+                  }
+                  QTimer::singleShot(1000, this, [this]() {
+                          nhlog::ui()->debug("timeout");
+                          this->current_txn = "";
+                          emit processPending();
+                  });
+          },
+          Qt::QueuedConnection);
+
+        connect(
+          this,
+          &EventStore::messageSent,
+          this,
+          [this](std::string txn_id, std::string event_id) {
+                  nhlog::ui()->debug("sent {}", txn_id);
+
+                  http::client()->read_event(
+                    room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
+                            if (err) {
+                                    nhlog::net()->warn(
+                                      "failed to read_event ({}, {})", room_id_, event_id);
+                            }
+                    });
+
+                  cache::client()->removePendingStatus(room_id_, txn_id);
+                  this->current_txn             = "";
+                  this->current_txn_error_count = 0;
+                  emit processPending();
+          },
+          Qt::QueuedConnection);
 }
 
 void
@@ -280,50 +284,77 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                 if (auto encrypted =
                       std::get_if>(
                         &event)) {
-                        auto event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+                        auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
                         if (std::visit(
                               [](auto e) { return (e.sender != utils::localUser().toStdString()); },
-                              *event)) {
-                                if (auto msg = std::get_if>(event)) {
+                              *d_event)) {
+                                if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         last_verification_request_event = *msg;
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         last_verification_cancel_event = *msg;
                                         ChatPage::instance()->recievedDeviceVerificationCancel(
                                           msg->content);
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         ChatPage::instance()->recievedDeviceVerificationAccept(
                                           msg->content);
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         ChatPage::instance()->recievedDeviceVerificationKey(
                                           msg->content);
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         ChatPage::instance()->recievedDeviceVerificationMac(
                                           msg->content);
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         ChatPage::instance()->recievedDeviceVerificationReady(
                                           msg->content);
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         ChatPage::instance()->recievedDeviceVerificationDone(
                                           msg->content);
-                                } else if (auto msg = std::get_if>(event)) {
+                                        continue;
+                                } else if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
                                         ChatPage::instance()->recievedDeviceVerificationStart(
                                           msg->content, msg->sender);
+                                        continue;
+                                }
+                        } else {
+                                // only the key.verification.ready sent by localuser's other device
+                                // is of significance as it is used for detecting accepted request
+                                if (std::get_if>(d_event)) {
+                                        auto msg = std::get_if>(d_event);
+                                        ChatPage::instance()->recievedDeviceVerificationReady(
+                                          msg->content);
                                 }
-                        }
-                        // only the key.verification.ready sent by localuser's other device is of
-                        // significance as it is used for detecting accepted request
-                        if (auto msg = std::get_if<
-                              mtx::events::RoomEvent>(
-                              event)) {
-                                ChatPage::instance()->recievedDeviceVerificationReady(msg->content);
                         }
                 }
         }
@@ -614,12 +645,6 @@ EventStore::decryptEvent(const IdIndex &idx,
                 return asCacheEntry(std::move(temp_events[0]));
         }
 
-        dummy.content.body = tr("-- Encrypted Event (Unknown event type) --",
-                                "Placeholder, when the message was decrypted, but we "
-                                "couldn't parse it, because "
-                                "Nheko/mtxclient don't support that event type yet.")
-                               .toStdString();
-        return asCacheEntry(std::move(dummy));
         auto encInfo = mtx::accessors::file(decryptionResult.event.value());
         if (encInfo)
                 emit newEncryptedImage(encInfo.value());
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 8f0e470..570186a 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -204,11 +204,12 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
   , room_id_(room_id)
   , manager_(manager)
 {
-        connect(this,
-                &TimelineModel::redactionFailed,
-                this,
-                [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &TimelineModel::redactionFailed,
+          this,
+          [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
+          Qt::QueuedConnection);
 
         connect(this,
                 &TimelineModel::newMessageToSend,
@@ -217,17 +218,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 Qt::QueuedConnection);
         connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
 
-        connect(&events,
-                &EventStore::dataChanged,
-                this,
-                [this](int from, int to) {
-                        nhlog::ui()->debug("data changed {} to {}",
-                                           events.size() - to - 1,
-                                           events.size() - from - 1);
-                        emit dataChanged(index(events.size() - to - 1, 0),
-                                         index(events.size() - from - 1, 0));
-                },
-                Qt::QueuedConnection);
+        connect(
+          &events,
+          &EventStore::dataChanged,
+          this,
+          [this](int from, int to) {
+                  nhlog::ui()->debug(
+                    "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
+                  emit dataChanged(index(events.size() - to - 1, 0),
+                                   index(events.size() - from - 1, 0));
+          },
+          Qt::QueuedConnection);
 
         connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
                 int first = events.size() - to;
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 3d0d298..de55b6a 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -59,7 +59,7 @@ public:
                 connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset);
         };
         QHash roleNames() const override;
-        int rowCount(const QModelIndex &parent = QModelIndex()) const
+        int rowCount(const QModelIndex &parent = QModelIndex()) const override
         {
                 (void)parent;
                 return (int)deviceList_.size();

From 898be090af24f43d4f2c20f1cbed40773c0d8312 Mon Sep 17 00:00:00 2001
From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com>
Date: Tue, 8 Sep 2020 20:39:31 +0530
Subject: [PATCH 42/70] Add support non-encrypted room-verification messages

---
 src/timeline/EventStore.cpp | 117 ++++++++++++++++++------------------
 src/timeline/EventStore.h   |   1 +
 2 files changed, 60 insertions(+), 58 deletions(-)

diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index e5eaefb..af1f7b2 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -280,71 +280,18 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                         }
                 }
 
+                handle_room_verification(event);
+
                 // decrypting and checking some encrypted messages
                 if (auto encrypted =
                       std::get_if>(
                         &event)) {
-                        auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+                        mtx::events::collections::TimelineEvents *d_event =
+                          decryptEvent({room_id_, encrypted->event_id}, *encrypted);
                         if (std::visit(
                               [](auto e) { return (e.sender != utils::localUser().toStdString()); },
                               *d_event)) {
-                                if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        last_verification_request_event = *msg;
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        last_verification_cancel_event = *msg;
-                                        ChatPage::instance()->recievedDeviceVerificationCancel(
-                                          msg->content);
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        ChatPage::instance()->recievedDeviceVerificationAccept(
-                                          msg->content);
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        ChatPage::instance()->recievedDeviceVerificationKey(
-                                          msg->content);
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        ChatPage::instance()->recievedDeviceVerificationMac(
-                                          msg->content);
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        ChatPage::instance()->recievedDeviceVerificationReady(
-                                          msg->content);
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        ChatPage::instance()->recievedDeviceVerificationDone(
-                                          msg->content);
-                                        continue;
-                                } else if (std::get_if>(d_event)) {
-                                        auto msg = std::get_if>(d_event);
-                                        ChatPage::instance()->recievedDeviceVerificationStart(
-                                          msg->content, msg->sender);
-                                        continue;
-                                }
+                                handle_room_verification(*d_event);
                         } else {
                                 // only the key.verification.ready sent by localuser's other device
                                 // is of significance as it is used for detecting accepted request
@@ -368,6 +315,60 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
         }
 }
 
+void
+EventStore::handle_room_verification(mtx::events::collections::TimelineEvents event)
+{
+        if (std::get_if>(&event)) {
+                auto msg =
+                  std::get>(event);
+                last_verification_request_event = msg;
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                last_verification_cancel_event = msg;
+                ChatPage::instance()->recievedDeviceVerificationCancel(msg.content);
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                ChatPage::instance()->recievedDeviceVerificationAccept(msg.content);
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                ChatPage::instance()->recievedDeviceVerificationKey(msg.content);
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                ChatPage::instance()->recievedDeviceVerificationMac(msg.content);
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                ChatPage::instance()->recievedDeviceVerificationReady(msg.content);
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                ChatPage::instance()->recievedDeviceVerificationDone(msg.content);
+                return;
+        } else if (std::get_if>(
+                     &event)) {
+                auto msg =
+                  std::get>(event);
+                ChatPage::instance()->recievedDeviceVerificationStart(msg.content, msg.sender);
+                return;
+        }
+}
+
 QVariantList
 EventStore::reactions(const std::string &event_id)
 {
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 4ff4fa3..89a5147 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -110,6 +110,7 @@ private:
         mtx::events::collections::TimelineEvents *decryptEvent(
           const IdIndex &idx,
           const mtx::events::EncryptedEvent &e);
+        void handle_room_verification(mtx::events::collections::TimelineEvents event);
 
         std::string room_id_;
 

From e70b4e42680562a308d97ef34f2ad4eb3ac67bd1 Mon Sep 17 00:00:00 2001
From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com>
Date: Thu, 10 Sep 2020 09:56:14 +0530
Subject: [PATCH 43/70] Fix wrong tran_id issue

---
 resources/qml/TimelineView.qml                |  2 +-
 resources/qml/UserProfile.qml                 |  4 ++--
 .../DeviceVerification.qml                    | 19 ++++++++++---------
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index f2390b1..30158e3 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -118,7 +118,7 @@ Page {
 						deviceVerificationList.add(flow.tranId);
 						break;
 				}
-				var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow,isRequest = isRequest});
+				var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow,isRequest: isRequest,tran_id: flow.tranId});
 				dialog.show();
 			}
 		}
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 115a73c..30d9c95 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -94,7 +94,7 @@ ApplicationWindow{
 					newFlow.userId = profile.userid;
 					newFlow.sender = true;
 					deviceVerificationList.add(newFlow.tranId);
-					var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest: true});
+					var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest: true,tran_id: newFlow.tranId});
 					dialog.show();
 				}
 			}
@@ -206,7 +206,7 @@ ApplicationWindow{
 										deviceVerificationList.updateProfile(newFlow.userId);
 									}else{
 										deviceVerificationList.add(newFlow.tranId);
-										var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest:false});
+										var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest:false,tran_id: newFlow.tranId});
 										dialog.show();
 									}
 								}
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 94cb1e3..b5c53a4 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -24,6 +24,7 @@ ApplicationWindow {
 
 	property var flow
 	property bool isRequest
+	property var tran_id
 
 	Connections {
 		target: flow
@@ -82,6 +83,7 @@ ApplicationWindow {
                         }
 						onClicked: { 
 							dialog.close(); 
+							deviceVerificationList.remove(tran_id);
 							delete flow; 
 						}
 					}
@@ -141,7 +143,7 @@ ApplicationWindow {
 						onClicked: { 
 							dialog.close();
 							flow.cancelVerification(DeviceVerificationFlow.User);
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 						}
 					}
 					Item {
@@ -205,7 +207,7 @@ ApplicationWindow {
 						onClicked: { 
 							dialog.close(); 
 							flow.cancelVerification(DeviceVerificationFlow.User);
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 						}
 					}
 					Item {
@@ -267,7 +269,7 @@ ApplicationWindow {
 						onClicked: { 
 							dialog.close(); 
 							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 						}
 					}
 					Item {
@@ -426,7 +428,7 @@ ApplicationWindow {
 						onClicked: { 
 							dialog.close(); 
 							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 						}
 					}
 					Item {
@@ -487,8 +489,7 @@ ApplicationWindow {
 						onClicked: { 
 							dialog.close(); 
 							flow.cancelVerification(DeviceVerificationFlow.User); 
-							deviceVerificationList.remove(flow.tranId);
-							delete flow;
+							deviceVerificationList.remove(tran_id);
 						}
 					}
 					Item {
@@ -534,7 +535,7 @@ ApplicationWindow {
                         }
 						onClicked: {
 							dialog.close()
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 							delete flow;
 						}
 					}
@@ -578,7 +579,7 @@ ApplicationWindow {
                         }
 						onClicked: {
 							dialog.close();
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 						}
 					}
 				}
@@ -622,7 +623,7 @@ ApplicationWindow {
 						text: qsTr("Close")
 						onClicked: {
 							dialog.close()
-							deviceVerificationList.remove(flow.tranId);
+							deviceVerificationList.remove(tran_id);
 							delete flow;
 						}
 					}

From a27662dc0844cf9104967717076123ac73a667c5 Mon Sep 17 00:00:00 2001
From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com>
Date: Thu, 10 Sep 2020 13:25:49 +0530
Subject: [PATCH 44/70] Making sure Verification Objects are deleted properly

---
 .../DeviceVerification.qml                    | 28 +++++++++----------
 src/DeviceVerificationFlow.cpp                |  4 +++
 src/DeviceVerificationFlow.h                  |  1 +
 3 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index b5c53a4..6e4b462 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -82,9 +82,9 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: { 
-							dialog.close(); 
 							deviceVerificationList.remove(tran_id);
-							delete flow; 
+							flow.deleteFlow();
+							dialog.destroy();  
 						}
 					}
 					Item {
@@ -141,9 +141,9 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: { 
-							dialog.close();
 							flow.cancelVerification(DeviceVerificationFlow.User);
 							deviceVerificationList.remove(tran_id);
+							dialog.destroy();
 						}
 					}
 					Item {
@@ -205,9 +205,9 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: { 
-							dialog.close(); 
 							flow.cancelVerification(DeviceVerificationFlow.User);
 							deviceVerificationList.remove(tran_id);
+							dialog.destroy();
 						}
 					}
 					Item {
@@ -266,10 +266,10 @@ ApplicationWindow {
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
                         }
-						onClicked: { 
-							dialog.close(); 
+						onClicked: {
 							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
 							deviceVerificationList.remove(tran_id);
+							dialog.destroy();
 						}
 					}
 					Item {
@@ -425,10 +425,10 @@ ApplicationWindow {
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
                         }
-						onClicked: { 
-							dialog.close(); 
+						onClicked: {  
 							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
 							deviceVerificationList.remove(tran_id);
+							dialog.destroy();
 						}
 					}
 					Item {
@@ -487,9 +487,9 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: { 
-							dialog.close(); 
 							flow.cancelVerification(DeviceVerificationFlow.User); 
 							deviceVerificationList.remove(tran_id);
+							dialog.destroy();
 						}
 					}
 					Item {
@@ -534,9 +534,9 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: {
-							dialog.close()
 							deviceVerificationList.remove(tran_id);
-							delete flow;
+							flow.deleteFlow();
+							dialog.destroy();
 						}
 					}
 				}
@@ -578,8 +578,8 @@ ApplicationWindow {
                             verticalAlignment: Text.AlignVCenter
                         }
 						onClicked: {
-							dialog.close();
 							deviceVerificationList.remove(tran_id);
+							dialog.destroy();
 						}
 					}
 				}
@@ -622,9 +622,9 @@ ApplicationWindow {
                         }
 						text: qsTr("Close")
 						onClicked: {
-							dialog.close()
 							deviceVerificationList.remove(tran_id);
-							delete flow;
+							flow.deleteFlow();
+							dialog.destroy()
 						}
 					}
 				}
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index ae054af..70cc0ba 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -41,6 +41,10 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                 this->deleteLater();
         });
 
+        connect(this,&DeviceVerificationFlow::deleteFlow,this,[this](){
+                this->deleteLater();
+        });
+
         connect(
           ChatPage::instance(),
           &ChatPage::recievedDeviceVerificationStart,
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index c215098..b85cbec 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -105,6 +105,7 @@ signals:
         void timedout();
         void verificationCanceled();
         void refreshProfile();
+        void deleteFlow();
 
 private:
         // general

From 2b5deabbdc72f278ea112139999f6e91b8e571b7 Mon Sep 17 00:00:00 2001
From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com>
Date: Thu, 10 Sep 2020 14:50:10 +0530
Subject: [PATCH 45/70] Fix breaking while using qmlRegisterSingletonInstance

---
 src/DeviceVerificationFlow.cpp       | 4 +---
 src/timeline/TimelineViewManager.cpp | 8 ++++++--
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 70cc0ba..96fed55 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -41,9 +41,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                 this->deleteLater();
         });
 
-        connect(this,&DeviceVerificationFlow::deleteFlow,this,[this](){
-                this->deleteLater();
-        });
+        connect(this, &DeviceVerificationFlow::deleteFlow, this, [this]() { this->deleteLater(); });
 
         connect(
           ChatPage::instance(),
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index f199578..97c119b 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -140,8 +140,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           0,
           "UserProfileModel",
           "UserProfile needs to be instantiated on the C++ side");
-        qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", this);
-        qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", settings.data());
+        qmlRegisterSingletonType(
+          "im.nheko", 1, 0, "TimelineManager", [this](QQmlEngine *, QJSEngine *) { return this; });
+        qmlRegisterSingletonType(
+          "im.nheko", 1, 0, "Settings", [this](QQmlEngine *, QJSEngine *) {
+                  return this->settings.data();
+          });
 
         qRegisterMetaType();
         qRegisterMetaType>();

From f6a47ce72f69438a38a6d16fbba38a4b4075ce07 Mon Sep 17 00:00:00 2001
From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com>
Date: Sat, 12 Sep 2020 15:10:36 +0530
Subject: [PATCH 46/70] Some fixes

---
 resources/qml/UserProfile.qml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 30d9c95..374d9bf 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -33,7 +33,7 @@ ApplicationWindow{
 		DeviceVerificationFlow {}
 	}
 
-	background: Item{
+	Item{
 		id: userProfileItem
 		width: userProfileDialog.width
 		height: userProfileDialog.height
@@ -66,9 +66,9 @@ ApplicationWindow{
 				Layout.alignment: Qt.AlignHCenter
 			}
 
-			Label {
+			TextEdit {
 				text: profile.userid
-				fontSizeMode: Text.HorizontalFit
+				selectByMouse: true
 				font.pixelSize: 15
 				color: colors.text
 				Layout.alignment: Qt.AlignHCenter

From b934cf329c61c8790e295a16553a231ba8eeb209 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sun, 13 Sep 2020 18:13:08 +0200
Subject: [PATCH 47/70] Clean up UserProfile a bit

---
 resources/qml/UserProfile.qml | 340 ++++++++++++++--------------------
 1 file changed, 143 insertions(+), 197 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 374d9bf..b5a5916 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -13,13 +13,14 @@ ApplicationWindow{
 	id: userProfileDialog
 	height: 650
 	width: 420
+	minimumHeight: 420
+
 	modality: Qt.WindowModal
-	Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
 	palette: colors
 
 	Connections{
 		target: deviceVerificationList
-		onUpdateProfile: {
+		function onUpdateProfile() {
 			profile.fetchDeviceList(profile.userid)
 		}
 	}
@@ -33,226 +34,171 @@ ApplicationWindow{
 		DeviceVerificationFlow {}
 	}
 
-	Item{
-		id: userProfileItem
-		width: userProfileDialog.width
-		height: userProfileDialog.height
+	ColumnLayout{
+		id: contentL
 
-		// Layout.fillHeight : true
+		anchors.fill: parent
+		anchors.margins: 10
 
-		ColumnLayout{
-			anchors.fill: userProfileItem
-			width: userProfileDialog.width
-			spacing: 10
+		spacing: 10
 
-			Avatar {
-				url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
-				height: 130
-				width: 130
-				displayName: profile.displayName
-				userid: profile.userid
-				Layout.alignment: Qt.AlignHCenter
-				Layout.margins : {
-					top: 10
-				}
-			}
+		Avatar {
+			url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
+			height: 130
+			width: 130
+			displayName: profile.displayName
+			userid: profile.userid
+			Layout.alignment: Qt.AlignHCenter
+		}
 
-			Label {
-				text: profile.displayName
-				fontSizeMode: Text.HorizontalFit
-				font.pixelSize: 20
-				color: TimelineManager.userColor(profile.userid, colors.window)
-				font.bold: true
-				Layout.alignment: Qt.AlignHCenter
-			}
+		Label {
+			text: profile.displayName
+			fontSizeMode: Text.HorizontalFit
+			font.pixelSize: 20
+			color: TimelineManager.userColor(profile.userid, colors.window)
+			font.bold: true
+			Layout.alignment: Qt.AlignHCenter
+		}
 
-			TextEdit {
-				text: profile.userid
-				selectByMouse: true
-				font.pixelSize: 15
-				color: colors.text
-				Layout.alignment: Qt.AlignHCenter
-			}
+		MatrixText {
+			text: profile.userid
+			font.pixelSize: 15
+			Layout.alignment: Qt.AlignHCenter
+		}
 
-			Button {
-				id: verifyUserButton
-				text: "Verify"
-				Layout.alignment: Qt.AlignHCenter
-				enabled: profile.isUserVerified?false:true
-				visible: profile.isUserVerified?false:true
-				palette {
-					button: "white"
-				}
-				contentItem: Text {
-					text: verifyUserButton.text
-					color: "black"
-					horizontalAlignment: Text.AlignHCenter
-					verticalAlignment: Text.AlignVCenter
-				}
-				onClicked: {
-					var newFlow = profile.createFlow(true);
-					newFlow.userId = profile.userid;
-					newFlow.sender = true;
-					deviceVerificationList.add(newFlow.tranId);
-					var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest: true,tran_id: newFlow.tranId});
-					dialog.show();
-				}
+		Button {
+			id: verifyUserButton
+			text: "Verify"
+			Layout.alignment: Qt.AlignHCenter
+			enabled: profile.isUserVerified?false:true
+			visible: profile.isUserVerified?false:true
+
+			onClicked: {
+				var newFlow = profile.createFlow(true);
+				newFlow.userId = profile.userid;
+				newFlow.sender = true;
+				deviceVerificationList.add(newFlow.tranId);
+				var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest: true,tran_id: newFlow.tranId});
+				dialog.show();
 			}
+		}
 
-			RowLayout {
-				Layout.alignment: Qt.AlignHCenter
-				ImageButton {
-					image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
-					Layout.margins: {
-						left: 5
-						right: 5
-					}
-					ToolTip.visible: hovered
-					ToolTip.text: qsTr("Ban the user")
-					onClicked : {
-						profile.banUser()
-					}
+		RowLayout {
+			Layout.alignment: Qt.AlignHCenter
+			spacing: 8
+
+			ImageButton {
+				image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
+				hoverEnabled: true
+				ToolTip.visible: hovered
+				ToolTip.text: qsTr("Ban the user")
+				onClicked : {
+					profile.banUser()
 				}
-				// ImageButton{
-				//     image:":/icons/icons/ui/volume-off-indicator.png"
-				//     Layout.margins: {
-				//         left: 5
-				//         right: 5
-				//     }
-				//     ToolTip.visible: hovered
-				//     ToolTip.text: qsTr("Ignore messages from this user")
-				//     onClicked : {
-				//         profile.ignoreUser()
-				//     }
-				// }
-				ImageButton{
-					image:":/icons/icons/ui/black-bubble-speech.png"
-					Layout.margins: {
-						left: 5
-						right: 5
-					}
-					ToolTip.visible: hovered
-					ToolTip.text: qsTr("Start a private chat")
-					onClicked : {
-						profile.startChat()
-					}
+			}
+			// ImageButton{
+			//     image:":/icons/icons/ui/volume-off-indicator.png"
+			//     Layout.margins: {
+			//         left: 5
+			//         right: 5
+			//     }
+			//     ToolTip.visible: hovered
+			//     ToolTip.text: qsTr("Ignore messages from this user")
+			//     onClicked : {
+			//         profile.ignoreUser()
+			//     }
+			// }
+			ImageButton{
+				image:":/icons/icons/ui/black-bubble-speech.png"
+				hoverEnabled: true
+				ToolTip.visible: hovered
+				ToolTip.text: qsTr("Start a private chat")
+				onClicked : {
+					profile.startChat()
 				}
-				ImageButton{
-					image:":/icons/icons/ui/round-remove-button.png"
-					Layout.margins: {
-						left: 5
-						right: 5
-					}
-					ToolTip.visible: hovered
-					ToolTip.text: qsTr("Kick the user")
-					onClicked : {
-						profile.kickUser()
-					}
+			}
+			ImageButton{
+				image:":/icons/icons/ui/round-remove-button.png"
+				hoverEnabled: true
+				ToolTip.visible: hovered
+				ToolTip.text: qsTr("Kick the user")
+				onClicked : {
+					profile.kickUser()
 				}
 			}
+		}
 
-			ScrollView {
-				implicitHeight: userProfileDialog.height/2-13
-				implicitWidth: userProfileDialog.width-20
-				clip: true
-				Layout.alignment: Qt.AlignHCenter
+		ListView{
+			id: devicelist
 
-				ListView{
-					id: devicelist
-					anchors.fill: parent
-					clip: true
-					spacing: 4
+			Layout.fillHeight: true
+			Layout.minimumHeight: 200
+			Layout.fillWidth: true
 
-					model: profile.deviceList
+			clip: true
+			spacing: 8
+			boundsBehavior: Flickable.StopAtBounds
 
-					delegate: RowLayout{
-						width: parent.width
-						Layout.margins : {
-							top : 50
-						}
-						ColumnLayout{
-							Text{
-								Layout.fillWidth: true
-								color: colors.text
-								font.bold: true
-								Layout.alignment: Qt.AlignLeft
-								text: model.deviceId
-							}
-							Text{
-								Layout.fillWidth: true
-								color:colors.text
-								Layout.alignment: Qt.AlignRight
-								text: model.deviceName
-							}
-						}
-						RowLayout{
-							Image{
-								Layout.preferredWidth: 20
-								Layout.preferredHeight: 20
-								source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green":
-								((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow":
-								"image://colorimage/:/icons/icons/ui/unlock.png?red"))
-							}
-							Button{
-								id: verifyButton
-								text:(model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify"
-								onClicked: {
-									var newFlow = profile.createFlow(false);
-									newFlow.userId = profile.userid;
-									newFlow.sender = true;
-									newFlow.deviceId = model.deviceId;
-									if(model.verificationStatus == VerificationStatus.VERIFIED){
-										newFlow.unverify();
-										deviceVerificationList.updateProfile(newFlow.userId);
-									}else{
-										deviceVerificationList.add(newFlow.tranId);
-										var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest:false,tran_id: newFlow.tranId});
-										dialog.show();
-									}
-								}
-								Layout.margins:{
-									right: 10
-								}
-								palette {
-									button: "white"
-								}
-								contentItem: Text {
-									text: verifyButton.text
-									color: "black"
-									horizontalAlignment: Text.AlignHCenter
-									verticalAlignment: Text.AlignVCenter
-								}
-							}
-						}
-					}
-				}
-			}
+			model: profile.deviceList
 
-			Button{
-				id: okbutton
-				text:"OK"
-				onClicked: userProfileDialog.close()
+			delegate: RowLayout{
+				width: parent.width
+				spacing: 4
 
-				Layout.alignment: Qt.AlignRight | Qt.AlignBottom
+				ColumnLayout{
+					spacing: 0
+					Text{
+						Layout.fillWidth: true
+						Layout.alignment: Qt.AlignLeft
 
-				Layout.margins : {
-					right : 10
-					bottom: 5
-				}
+						elide: Text.ElideRight
+						color: colors.text
+						font.bold: true
+						text: model.deviceId
+					}
+					Text{
+						Layout.fillWidth: true
+						Layout.alignment: Qt.AlignRight
 
-				palette {
-					button: "white"
+						elide: Text.ElideRight
+						color:colors.text
+						text: model.deviceName
+					}
 				}
 
-				contentItem: Text {
-					text: okbutton.text
-					color: "black"
-					horizontalAlignment: Text.AlignHCenter
-					verticalAlignment: Text.AlignVCenter
+				Image{
+					Layout.preferredHeight: 16
+					Layout.preferredWidth: 16
+
+					source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green":
+					((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow":
+					"image://colorimage/:/icons/icons/ui/unlock.png?red"))
+				}
+				Button{
+					id: verifyButton
+					text: (model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify"
+					onClicked: {
+						var newFlow = profile.createFlow(false);
+						newFlow.userId = profile.userid;
+						newFlow.sender = true;
+						newFlow.deviceId = model.deviceId;
+						if(model.verificationStatus == VerificationStatus.VERIFIED){
+							newFlow.unverify();
+							deviceVerificationList.updateProfile(newFlow.userId);
+						}else{
+							deviceVerificationList.add(newFlow.tranId);
+							var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest:false,tran_id: newFlow.tranId});
+							dialog.show();
+						}
+					}
 				}
 			}
 		}
+	}
+
+	footer: DialogButtonBox {
+		standardButtons: DialogButtonBox.Ok
 
-		Item { Layout.fillHeight: true }
+		onAccepted: userProfileDialog.close()
 	}
 }

From 94e1b52dddc1d49c60e0372953855651e33d7425 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sun, 13 Sep 2020 18:23:41 +0200
Subject: [PATCH 48/70] Single line click handler

---
 resources/qml/UserProfile.qml | 20 +++++++-------------
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index b5a5916..9d59184 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -70,8 +70,8 @@ ApplicationWindow{
 			id: verifyUserButton
 			text: "Verify"
 			Layout.alignment: Qt.AlignHCenter
-			enabled: profile.isUserVerified?false:true
-			visible: profile.isUserVerified?false:true
+			enabled: profile.isUserVerified
+			visible: profile.isUserVerified
 
 			onClicked: {
 				var newFlow = profile.createFlow(true);
@@ -92,9 +92,7 @@ ApplicationWindow{
 				hoverEnabled: true
 				ToolTip.visible: hovered
 				ToolTip.text: qsTr("Ban the user")
-				onClicked : {
-					profile.banUser()
-				}
+				onClicked: profile.banUser()
 			}
 			// ImageButton{
 			//     image:":/icons/icons/ui/volume-off-indicator.png"
@@ -113,18 +111,14 @@ ApplicationWindow{
 				hoverEnabled: true
 				ToolTip.visible: hovered
 				ToolTip.text: qsTr("Start a private chat")
-				onClicked : {
-					profile.startChat()
-				}
+				onClicked: profile.startChat()
 			}
 			ImageButton{
 				image:":/icons/icons/ui/round-remove-button.png"
 				hoverEnabled: true
 				ToolTip.visible: hovered
 				ToolTip.text: qsTr("Kick the user")
-				onClicked : {
-					profile.kickUser()
-				}
+				onClicked: profile.kickUser()
 			}
 		}
 
@@ -152,8 +146,8 @@ ApplicationWindow{
 						Layout.alignment: Qt.AlignLeft
 
 						elide: Text.ElideRight
-						color: colors.text
 						font.bold: true
+						color: colors.text
 						text: model.deviceId
 					}
 					Text{
@@ -161,7 +155,7 @@ ApplicationWindow{
 						Layout.alignment: Qt.AlignRight
 
 						elide: Text.ElideRight
-						color:colors.text
+						color: colors.text
 						text: model.deviceName
 					}
 				}

From 8eb74daf766ea32804171154d45cb28e32e39e40 Mon Sep 17 00:00:00 2001
From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>
Date: Mon, 14 Sep 2020 17:27:49 +0530
Subject: [PATCH 49/70] Split qml part of Device Verification

---
 resources/qml/UserProfile.qml                 |   4 +-
 .../AcceptNewVerificationRequest.qml          |  65 ++
 .../AwaitingVerificationConfirmation.qml      |  48 ++
 .../AwaitingVerificationRequest.qml           |  48 ++
 .../DeviceVerification.qml                    | 621 +-----------------
 .../device-verification/DigitVerification.qml |  80 +++
 .../qml/device-verification/EmojiElement.qml  |   1 +
 .../device-verification/EmojiVerification.qml | 160 +++++
 .../NewVerificationRequest.qml                |  71 ++
 .../device-verification/PartnerAborted.qml    |  42 ++
 .../qml/device-verification/TimedOut.qml      |  44 ++
 .../VerificationSuccess.qml                   |  43 ++
 resources/res.qrc                             |   9 +
 src/Cache.cpp                                 |  38 +-
 src/ChatPage.cpp                              |  11 +-
 src/emoji/EmojiSearchModel.h                  |   1 -
 src/timeline/EventStore.cpp                   | 148 ++---
 src/timeline/TimelineModel.cpp                |  33 +-
 18 files changed, 757 insertions(+), 710 deletions(-)
 create mode 100644 resources/qml/device-verification/AcceptNewVerificationRequest.qml
 create mode 100644 resources/qml/device-verification/AwaitingVerificationConfirmation.qml
 create mode 100644 resources/qml/device-verification/AwaitingVerificationRequest.qml
 create mode 100644 resources/qml/device-verification/DigitVerification.qml
 create mode 100644 resources/qml/device-verification/EmojiVerification.qml
 create mode 100644 resources/qml/device-verification/NewVerificationRequest.qml
 create mode 100644 resources/qml/device-verification/PartnerAborted.qml
 create mode 100644 resources/qml/device-verification/TimedOut.qml
 create mode 100644 resources/qml/device-verification/VerificationSuccess.qml

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 9d59184..1ca9dcc 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -70,8 +70,8 @@ ApplicationWindow{
 			id: verifyUserButton
 			text: "Verify"
 			Layout.alignment: Qt.AlignHCenter
-			enabled: profile.isUserVerified
-			visible: profile.isUserVerified
+			enabled: !profile.isUserVerified
+			visible: !profile.isUserVerified
 
 			onClicked: {
 				var newFlow = profile.createFlow(true);
diff --git a/resources/qml/device-verification/AcceptNewVerificationRequest.qml b/resources/qml/device-verification/AcceptNewVerificationRequest.qml
new file mode 100644
index 0000000..872fabe
--- /dev/null
+++ b/resources/qml/device-verification/AcceptNewVerificationRequest.qml
@@ -0,0 +1,65 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+import im.nheko 1.0
+
+Pane {
+	property string title: qsTr("Recieving Device Verification Request")
+	Component {
+		id: awaitingVerificationRequestAccept
+		AwaitingVerificationRequest {}
+	}
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			text: qsTr("The device was requested to be verified")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Button {
+				Layout.alignment: Qt.AlignLeft
+				text: qsTr("Deny")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { 
+					flow.cancelVerification(DeviceVerificationFlow.User);
+					deviceVerificationList.remove(tran_id);
+					dialog.destroy();
+				}
+			}
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				Layout.alignment: Qt.AlignRight
+				text: qsTr("Accept")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { 
+					stack.replace(awaitingVerificationRequestAccept); 
+					isRequest?flow.sendVerificationReady():flow.acceptVerificationRequest(); 
+				}
+			}
+		}
+	}
+}
diff --git a/resources/qml/device-verification/AwaitingVerificationConfirmation.qml b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml
new file mode 100644
index 0000000..e078634
--- /dev/null
+++ b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml
@@ -0,0 +1,48 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+import im.nheko 1.0
+
+Pane {
+	property string title: qsTr("Awaiting Confirmation")
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			id: content
+			text: qsTr("Waiting for other side to complete verification.")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		BusyIndicator {
+			Layout.alignment: Qt.AlignHCenter
+		}
+		RowLayout {
+			Button {
+				Layout.alignment: Qt.AlignLeft
+				text: qsTr("Cancel")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { 
+					flow.cancelVerification(DeviceVerificationFlow.User); 
+					deviceVerificationList.remove(tran_id);
+					dialog.destroy();
+				}
+			}
+			Item {
+				Layout.fillWidth: true
+			}
+		}
+	}
+}
diff --git a/resources/qml/device-verification/AwaitingVerificationRequest.qml b/resources/qml/device-verification/AwaitingVerificationRequest.qml
new file mode 100644
index 0000000..22a504c
--- /dev/null
+++ b/resources/qml/device-verification/AwaitingVerificationRequest.qml
@@ -0,0 +1,48 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+import im.nheko 1.0
+
+Pane {
+	property string title: qsTr("Waiting for other party")
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			id: content
+			text: qsTr("Waiting for other side to accept the verification request.")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		BusyIndicator {
+			Layout.alignment: Qt.AlignHCenter
+		}
+		RowLayout {
+			Button {
+				Layout.alignment: Qt.AlignLeft
+				text: qsTr("Cancel")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { 
+					flow.cancelVerification(DeviceVerificationFlow.User);
+					deviceVerificationList.remove(tran_id);
+					dialog.destroy();
+				}
+			}
+			Item {
+				Layout.fillWidth: true
+			}
+		}
+	}
+}
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 6e4b462..e409b0f 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -1,11 +1,14 @@
 import QtQuick 2.3
 import QtQuick.Controls 2.10
 import QtQuick.Window 2.2
-import QtQuick.Layouts 1.10
 
 import im.nheko 1.0
 
 ApplicationWindow {
+	property var flow
+	property bool isRequest
+	property var tran_id
+
 	title: stack.currentItem.title
 	id: dialog
 
@@ -15,6 +18,17 @@ ApplicationWindow {
 
 	height: stack.implicitHeight
 	width: stack.implicitWidth
+
+	Component{
+		id: newVerificationRequest
+		NewVerificationRequest {}
+	}
+
+	Component{
+		id: acceptNewVerificationRequest
+		AcceptNewVerificationRequest {}
+	}
+
 	StackView {
 		id: stack
 		initialItem: flow.sender == true?newVerificationRequest:acceptNewVerificationRequest
@@ -22,613 +36,44 @@ ApplicationWindow {
 		implicitHeight: currentItem.implicitHeight
 	}
 
-	property var flow
-	property bool isRequest
-	property var tran_id
-
-	Connections {
-		target: flow
-		onVerificationCanceled: stack.replace(partnerAborted)
-		onTimedout: stack.replace(timedout)
-		onDeviceVerified: stack.replace(verificationSuccess)
-
-		onVerificationRequestAccepted: switch(method) {
-			case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break;
-			case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break;
-		}
-
-		onRefreshProfile: {
-			deviceVerificationList.updateProfile(flow.userId);
-		}
-	}
-
 	Component {
-		id: newVerificationRequest
-		Pane {
-			property string title: qsTr("Sending Device Verification Request")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					text: qsTr("A new device was added.")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					text: qsTr("The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device.")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				RowLayout {
-					Button {
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("Cancel")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { 
-							deviceVerificationList.remove(tran_id);
-							flow.deleteFlow();
-							dialog.destroy();  
-						}
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("Start verification")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { 
-							stack.replace(awaitingVerificationRequestAccept); 
-							isRequest?flow.sendVerificationRequest():flow.startVerificationRequest(); }
-					}
-				}
-			}
-		}
+		id: partnerAborted
+		PartnerAborted {}
 	}
 
 	Component {
-		id: acceptNewVerificationRequest
-		Pane {
-			property string title: qsTr("Recieving Device Verification Request")
-			ColumnLayout {
-				spacing: 16
-
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					text: qsTr("The device was requested to be verified")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-				RowLayout {
-					Button {
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("Deny")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { 
-							flow.cancelVerification(DeviceVerificationFlow.User);
-							deviceVerificationList.remove(tran_id);
-							dialog.destroy();
-						}
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("Accept")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { 
-							stack.replace(awaitingVerificationRequestAccept); 
-							isRequest?flow.sendVerificationReady():flow.acceptVerificationRequest(); 
-						}
-					}
-				}
-			}
-		}
+		id: timedout
+		TimedOut {}
 	}
 
 	Component {
-		id: awaitingVerificationRequestAccept
-		Pane {
-			property string title: qsTr("Waiting for other party")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					id: content
-					text: qsTr("Waiting for other side to accept the verification request.")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				BusyIndicator {
-					Layout.alignment: Qt.AlignHCenter
-				}
-				RowLayout {
-					Button {
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("Cancel")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { 
-							flow.cancelVerification(DeviceVerificationFlow.User);
-							deviceVerificationList.remove(tran_id);
-							dialog.destroy();
-						}
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-				}
-			}
-		}
+		id: verificationSuccess
+		VerificationSuccess {}
 	}
 
 	Component {
 		id: digitVerification
-		Pane {
-			property string title: qsTr("Verification Code")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				RowLayout {
-					Layout.alignment: Qt.AlignHCenter
-					Label {
-						font.pixelSize: Qt.application.font.pixelSize * 2
-						text: flow.sasList[0]
-						color:colors.text
-					}
-					Label {
-						font.pixelSize: Qt.application.font.pixelSize * 2
-						text: flow.sasList[1]
-						color:colors.text
-					}
-					Label {
-						font.pixelSize: Qt.application.font.pixelSize * 2
-						text: flow.sasList[2]
-						color:colors.text
-					}
-				}
-
-				RowLayout {
-					Button {
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("They do not match!")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: {
-							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
-							deviceVerificationList.remove(tran_id);
-							dialog.destroy();
-						}
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("They match!")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); }
-					}
-				}
-			}
-		}
+		DigitVerification {}
 	}
 
 	Component {
 		id: emojiVerification
-		Pane {
-			property string title: qsTr("Verification Code")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				RowLayout {
-					Layout.alignment: Qt.AlignHCenter
-
-					id: emojis
-
-					property var mapping: [
-						{"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"},
-						{"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"},
-						{"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"},
-						{"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"},
-						{"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"},
-						{"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"},
-						{"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"},
-						{"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"},
-						{"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"},
-						{"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"},
-						{"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"},
-						{"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"},
-						{"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"},
-						{"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"},
-						{"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"},
-						{"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"},
-						{"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"},
-						{"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"},
-						{"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"},
-						{"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"},
-						{"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"},
-						{"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"},
-						{"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"},
-						{"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"},
-						{"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"},
-						{"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"},
-						{"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"},
-						{"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"},
-						{"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"},
-						{"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"},
-						{"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"},
-						{"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"},
-						{"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"},
-						{"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"},
-						{"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"},
-						{"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"},
-						{"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"},
-						{"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"},
-						{"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"},
-						{"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"},
-						{"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"},
-						{"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"},
-						{"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"},
-						{"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"},
-						{"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"},
-						{"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"},
-						{"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"},
-						{"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"},
-						{"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"},
-						{"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"},
-						{"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"},
-						{"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"},
-						{"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"},
-						{"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"},
-						{"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"},
-						{"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"},
-						{"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"},
-						{"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"},
-						{"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"},
-						{"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"},
-						{"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"},
-						{"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"},
-						{"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"},
-						{"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"}
-					]
-
-					Repeater {
-						id: repeater
-						model: 7
-						delegate: Rectangle {
-							color: "transparent"
-							implicitHeight: Qt.application.font.pixelSize * 8
-							implicitWidth: col.width
-							ColumnLayout {
-								id: col
-								Layout.fillWidth: true
-								anchors.bottom: parent.bottom
-								property var emoji: emojis.mapping[flow.sasList[index]]
-								Label {
-									//height: font.pixelSize * 2
-									Layout.alignment: Qt.AlignHCenter
-									text: col.emoji.emoji
-									font.pixelSize: Qt.application.font.pixelSize * 2
-									font.family: Settings.emojiFont
-									color:colors.text
-								}
-								Label {
-									Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
-									text: col.emoji.description
-									color:colors.text
-								}
-							}
-						}
-					}
-				}
-
-				RowLayout {
-					Button {
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("They do not match!")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: {  
-							flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
-							deviceVerificationList.remove(tran_id);
-							dialog.destroy();
-						}
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("They match!")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); }
-					}
-				}
-			}
-		}
-	}
-
-	Component {
-		id: awaitingVerificationConfirmation
-		Pane {
-			property string title: qsTr("Awaiting Confirmation")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					id: content
-					text: qsTr("Waiting for other side to complete verification.")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				BusyIndicator {
-					Layout.alignment: Qt.AlignHCenter
-				}
-				RowLayout {
-					Button {
-						Layout.alignment: Qt.AlignLeft
-						text: qsTr("Cancel")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: { 
-							flow.cancelVerification(DeviceVerificationFlow.User); 
-							deviceVerificationList.remove(tran_id);
-							dialog.destroy();
-						}
-					}
-					Item {
-						Layout.fillWidth: true
-					}
-				}
-			}
-		}
+		EmojiVerification {}
 	}
 
-	Component {
-		id: verificationSuccess
-		Pane {
-			property string title: qsTr("Successful Verification")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					id: content
-					text: qsTr("Verification successful! Both sides verified their devices!")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				RowLayout {
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("Close")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: {
-							deviceVerificationList.remove(tran_id);
-							flow.deleteFlow();
-							dialog.destroy();
-						}
-					}
-				}
-			}
-		}
-	}
-
-	Component {
-		id: partnerAborted
-		Pane {
-			property string title: qsTr("Verification aborted!")
-			ColumnLayout {
-				spacing: 16
-				Label {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					id: content
-					text: qsTr("Verification canceled by the other party!")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
+	Connections {
+		target: flow
+		onVerificationCanceled: stack.replace(partnerAborted)
+		onTimedout: stack.replace(timedout)
+		onDeviceVerified: stack.replace(verificationSuccess)
 
-				RowLayout {
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						Layout.alignment: Qt.AlignRight
-						text: qsTr("Close")
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						onClicked: {
-							deviceVerificationList.remove(tran_id);
-							dialog.destroy();
-						}
-					}
-				}
-			}
+		onVerificationRequestAccepted: switch(method) {
+			case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break;
+			case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break;
 		}
-	}
 
-	Component {
-		id: timedout
-		Pane {
-			property string title: qsTr("Verification timed out")
-			ColumnLayout {
-				spacing: 16
-				Text {
-					Layout.maximumWidth: 400
-					Layout.fillHeight: true
-					Layout.fillWidth: true
-					wrapMode: Text.Wrap
-					id: content
-					text: qsTr("Device verification timed out.")
-					color:colors.text
-					verticalAlignment: Text.AlignVCenter
-				}
-
-				RowLayout {
-					Item {
-						Layout.fillWidth: true
-					}
-					Button {
-						id: timedOutCancel
-						Layout.alignment: Qt.AlignRight
-						palette {
-                            button: "white"
-                        }
-						contentItem: Text {
-                            text: parent.text
-                            color: "black"
-                            horizontalAlignment: Text.AlignHCenter
-                            verticalAlignment: Text.AlignVCenter
-                        }
-						text: qsTr("Close")
-						onClicked: {
-							deviceVerificationList.remove(tran_id);
-							flow.deleteFlow();
-							dialog.destroy()
-						}
-					}
-				}
-			}
+		onRefreshProfile: {
+			deviceVerificationList.updateProfile(flow.userId);
 		}
 	}
 }
diff --git a/resources/qml/device-verification/DigitVerification.qml b/resources/qml/device-verification/DigitVerification.qml
new file mode 100644
index 0000000..241ccbd
--- /dev/null
+++ b/resources/qml/device-verification/DigitVerification.qml
@@ -0,0 +1,80 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+import im.nheko 1.0
+
+Pane {
+	property string title: qsTr("Verification Code")
+	Component {
+		id: awaitingVerificationConfirmation
+		AwaitingVerificationConfirmation {}
+	}
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Layout.alignment: Qt.AlignHCenter
+			Label {
+				font.pixelSize: Qt.application.font.pixelSize * 2
+				text: flow.sasList[0]
+				color:colors.text
+			}
+			Label {
+				font.pixelSize: Qt.application.font.pixelSize * 2
+				text: flow.sasList[1]
+				color:colors.text
+			}
+			Label {
+				font.pixelSize: Qt.application.font.pixelSize * 2
+				text: flow.sasList[2]
+				color:colors.text
+			}
+		}
+		RowLayout {
+			Button {
+				Layout.alignment: Qt.AlignLeft
+				text: qsTr("They do not match!")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: {
+					flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
+					deviceVerificationList.remove(tran_id);
+					dialog.destroy();
+				}
+			}
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				Layout.alignment: Qt.AlignRight
+				text: qsTr("They match!")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); }
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/resources/qml/device-verification/EmojiElement.qml b/resources/qml/device-verification/EmojiElement.qml
index 22f9e41..7e36459 100644
--- a/resources/qml/device-verification/EmojiElement.qml
+++ b/resources/qml/device-verification/EmojiElement.qml
@@ -1,4 +1,5 @@
 import QtQuick 2.3
+import QtQuick.Controls 2.10
 import QtQuick.Layouts 1.10
 
 Rectangle {
diff --git a/resources/qml/device-verification/EmojiVerification.qml b/resources/qml/device-verification/EmojiVerification.qml
new file mode 100644
index 0000000..fae08f2
--- /dev/null
+++ b/resources/qml/device-verification/EmojiVerification.qml
@@ -0,0 +1,160 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+import im.nheko 1.0
+
+Pane {
+	property string title: qsTr("Verification Code")
+	Component {
+		id: awaitingVerificationConfirmation
+		AwaitingVerificationConfirmation{}
+	}
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Layout.alignment: Qt.AlignHCenter
+			id: emojis
+			property var mapping: [
+				{"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"},
+				{"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"},
+				{"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"},
+				{"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"},
+				{"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"},
+				{"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"},
+				{"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"},
+				{"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"},
+				{"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"},
+				{"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"},
+				{"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"},
+				{"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"},
+				{"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"},
+				{"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"},
+				{"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"},
+				{"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"},
+				{"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"},
+				{"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"},
+				{"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"},
+				{"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"},
+				{"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"},
+				{"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"},
+				{"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"},
+				{"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"},
+				{"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"},
+				{"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"},
+				{"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"},
+				{"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"},
+				{"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"},
+				{"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"},
+				{"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"},
+				{"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"},
+				{"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"},
+				{"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"},
+				{"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"},
+				{"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"},
+				{"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"},
+				{"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"},
+				{"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"},
+				{"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"},
+				{"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"},
+				{"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"},
+				{"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"},
+				{"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"},
+				{"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"},
+				{"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"},
+				{"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"},
+				{"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"},
+				{"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"},
+				{"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"},
+				{"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"},
+				{"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"},
+				{"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"},
+				{"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"},
+				{"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"},
+				{"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"},
+				{"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"},
+				{"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"},
+				{"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"},
+				{"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"},
+				{"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"},
+				{"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"},
+				{"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"},
+				{"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"}
+			]
+			Repeater {
+				id: repeater
+				model: 7
+				delegate: Rectangle {
+					color: "transparent"
+					implicitHeight: Qt.application.font.pixelSize * 8
+					implicitWidth: col.width
+					ColumnLayout {
+						id: col
+						Layout.fillWidth: true
+						anchors.bottom: parent.bottom
+						property var emoji: emojis.mapping[flow.sasList[index]]
+						Label {
+							//height: font.pixelSize * 2
+							Layout.alignment: Qt.AlignHCenter
+							text: col.emoji.emoji
+							font.pixelSize: Qt.application.font.pixelSize * 2
+							font.family: Settings.emojiFont
+							color:colors.text
+						}
+						Label {
+							Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+							text: col.emoji.description
+							color:colors.text
+						}
+					}
+				}
+			}
+		}
+		RowLayout {
+			Button {
+				Layout.alignment: Qt.AlignLeft
+				text: qsTr("They do not match!")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: {  
+					flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS);
+					deviceVerificationList.remove(tran_id);
+					dialog.destroy();
+				}
+			}
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				Layout.alignment: Qt.AlignRight
+				text: qsTr("They match!")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); }
+			}
+		}
+	}
+}
diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml
new file mode 100644
index 0000000..d8fc65a
--- /dev/null
+++ b/resources/qml/device-verification/NewVerificationRequest.qml
@@ -0,0 +1,71 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+Pane {
+	property string title: qsTr("Sending Device Verification Request")
+	Component {
+		id: awaitingVerificationRequestAccept
+		AwaitingVerificationRequest {}
+	}
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			text: qsTr("A new device was added.")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			text: qsTr("The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device.")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Button {
+				Layout.alignment: Qt.AlignLeft
+				text: qsTr("Cancel")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { 
+					deviceVerificationList.remove(tran_id);
+					flow.deleteFlow();
+					dialog.destroy();  
+				}
+			}
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				Layout.alignment: Qt.AlignRight
+				text: qsTr("Start verification")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: { 
+					stack.replace(awaitingVerificationRequestAccept); 
+					isRequest?flow.sendVerificationRequest():flow.startVerificationRequest(); }
+			}
+		}
+	}
+}
diff --git a/resources/qml/device-verification/PartnerAborted.qml b/resources/qml/device-verification/PartnerAborted.qml
new file mode 100644
index 0000000..62787b1
--- /dev/null
+++ b/resources/qml/device-verification/PartnerAborted.qml
@@ -0,0 +1,42 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+Pane {
+	property string title: qsTr("Verification aborted!")
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			id: content
+			text: qsTr("Verification canceled by the other party!")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				Layout.alignment: Qt.AlignRight
+				text: qsTr("Close")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: {
+					deviceVerificationList.remove(tran_id);
+					dialog.destroy();
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/resources/qml/device-verification/TimedOut.qml b/resources/qml/device-verification/TimedOut.qml
new file mode 100644
index 0000000..4052869
--- /dev/null
+++ b/resources/qml/device-verification/TimedOut.qml
@@ -0,0 +1,44 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+Pane {
+	property string title: qsTr("Verification timed out")
+	ColumnLayout {
+		spacing: 16
+		Text {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			id: content
+			text: qsTr("Device verification timed out.")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				id: timedOutCancel
+				Layout.alignment: Qt.AlignRight
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				text: qsTr("Close")
+				onClicked: {
+					deviceVerificationList.remove(tran_id);
+					flow.deleteFlow();
+					dialog.destroy()
+				}
+			}
+		}
+	}
+}
diff --git a/resources/qml/device-verification/VerificationSuccess.qml b/resources/qml/device-verification/VerificationSuccess.qml
new file mode 100644
index 0000000..c87488d
--- /dev/null
+++ b/resources/qml/device-verification/VerificationSuccess.qml
@@ -0,0 +1,43 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.10
+import QtQuick.Layouts 1.10
+
+Pane {
+	property string title: qsTr("Successful Verification")
+	ColumnLayout {
+		spacing: 16
+		Label {
+			Layout.maximumWidth: 400
+			Layout.fillHeight: true
+			Layout.fillWidth: true
+			wrapMode: Text.Wrap
+			id: content
+			text: qsTr("Verification successful! Both sides verified their devices!")
+			color:colors.text
+			verticalAlignment: Text.AlignVCenter
+		}
+		RowLayout {
+			Item {
+				Layout.fillWidth: true
+			}
+			Button {
+				Layout.alignment: Qt.AlignRight
+				text: qsTr("Close")
+				palette {
+                    button: "white"
+                }
+				contentItem: Text {
+                    text: parent.text
+                    color: "black"
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                }
+				onClicked: {
+					deviceVerificationList.remove(tran_id);
+					if(flow) flow.deleteFlow();
+					dialog.destroy();
+				}
+			}
+		}
+	}
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index e8f1f7b..7ef7ecf 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -141,7 +141,16 @@
         qml/delegates/Pill.qml
         qml/delegates/Placeholder.qml
         qml/delegates/Reply.qml
+        qml/device-verification/AcceptNewVerificationRequest.qml
+        qml/device-verification/AwaitingVerificationConfirmation.qml
+        qml/device-verification/AwaitingVerificationRequest.qml
         qml/device-verification/DeviceVerification.qml
+        qml/device-verification/DigitVerification.qml
+        qml/device-verification/EmojiVerification.qml
+        qml/device-verification/NewVerificationRequest.qml
+        qml/device-verification/PartnerAborted.qml
+        qml/device-verification/TimedOut.qml
+        qml/device-verification/VerificationSuccess.qml
     
     
         media/ring.ogg
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 07d0181..5302218 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -139,26 +139,24 @@ Cache::Cache(const QString &userId, QObject *parent)
   , localUserId_{userId}
 {
         setup();
-        connect(
-          this,
-          &Cache::updateUserCacheFlag,
-          this,
-          [this](const std::string &user_id) {
-                  std::optional cache_ = getUserCache(user_id);
-                  if (cache_.has_value()) {
-                          cache_.value().isUpdated = false;
-                          setUserCache(user_id, cache_.value());
-                  } else {
-                          setUserCache(user_id, UserCache{});
-                  }
-          },
-          Qt::QueuedConnection);
-        connect(
-          this,
-          &Cache::deleteLeftUsers,
-          this,
-          [this](const std::string &user_id) { deleteUserCache(user_id); },
-          Qt::QueuedConnection);
+        connect(this,
+                &Cache::updateUserCacheFlag,
+                this,
+                [this](const std::string &user_id) {
+                        std::optional cache_ = getUserCache(user_id);
+                        if (cache_.has_value()) {
+                                cache_.value().isUpdated = false;
+                                setUserCache(user_id, cache_.value());
+                        } else {
+                                setUserCache(user_id, UserCache{});
+                        }
+                },
+                Qt::QueuedConnection);
+        connect(this,
+                &Cache::deleteLeftUsers,
+                this,
+                [this](const std::string &user_id) { deleteUserCache(user_id); },
+                Qt::QueuedConnection);
 }
 
 void
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 704543b..31ba38d 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -606,12 +606,11 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(
           this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
         connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
-        connect(
-          this,
-          &ChatPage::tryDelayedSyncCb,
-          this,
-          [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
-          Qt::QueuedConnection);
+        connect(this,
+                &ChatPage::tryDelayedSyncCb,
+                this,
+                [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
+                Qt::QueuedConnection);
 
         connect(this,
                 &ChatPage::newSyncResponse,
diff --git a/src/emoji/EmojiSearchModel.h b/src/emoji/EmojiSearchModel.h
index 1ff5f4e..13a0393 100644
--- a/src/emoji/EmojiSearchModel.h
+++ b/src/emoji/EmojiSearchModel.h
@@ -33,5 +33,4 @@ private:
                 return shortname.replace(" ", "-").replace(":", "-").replace("--", "-").toLower();
         }
 };
-
 }
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index af1f7b2..66a6d79 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -32,40 +32,38 @@ EventStore::EventStore(std::string room_id, QObject *)
                 this->last  = range->last;
         }
 
-        connect(
-          this,
-          &EventStore::eventFetched,
-          this,
-          [this](std::string id,
-                 std::string relatedTo,
-                 mtx::events::collections::TimelineEvents timeline) {
-                  cache::client()->storeEvent(room_id_, id, {timeline});
-
-                  if (!relatedTo.empty()) {
-                          auto idx = idToIndex(relatedTo);
-                          if (idx)
-                                  emit dataChanged(*idx, *idx);
-                  }
-          },
-          Qt::QueuedConnection);
-
-        connect(
-          this,
-          &EventStore::oldMessagesRetrieved,
-          this,
-          [this](const mtx::responses::Messages &res) {
-                  uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
-                  if (newFirst == first)
-                          fetchMore();
-                  else {
-                          emit beginInsertRows(toExternalIdx(newFirst),
-                                               toExternalIdx(this->first - 1));
-                          this->first = newFirst;
-                          emit endInsertRows();
-                          emit fetchedMore();
-                  }
-          },
-          Qt::QueuedConnection);
+        connect(this,
+                &EventStore::eventFetched,
+                this,
+                [this](std::string id,
+                       std::string relatedTo,
+                       mtx::events::collections::TimelineEvents timeline) {
+                        cache::client()->storeEvent(room_id_, id, {timeline});
+
+                        if (!relatedTo.empty()) {
+                                auto idx = idToIndex(relatedTo);
+                                if (idx)
+                                        emit dataChanged(*idx, *idx);
+                        }
+                },
+                Qt::QueuedConnection);
+
+        connect(this,
+                &EventStore::oldMessagesRetrieved,
+                this,
+                [this](const mtx::responses::Messages &res) {
+                        uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
+                        if (newFirst == first)
+                                fetchMore();
+                        else {
+                                emit beginInsertRows(toExternalIdx(newFirst),
+                                                     toExternalIdx(this->first - 1));
+                                this->first = newFirst;
+                                emit endInsertRows();
+                                emit fetchedMore();
+                        }
+                },
+                Qt::QueuedConnection);
 
         connect(this, &EventStore::processPending, this, [this]() {
                 if (!current_txn.empty()) {
@@ -130,48 +128,46 @@ EventStore::EventStore(std::string room_id, QObject *)
                   event->data);
         });
 
-        connect(
-          this,
-          &EventStore::messageFailed,
-          this,
-          [this](std::string txn_id) {
-                  if (current_txn == txn_id) {
-                          current_txn_error_count++;
-                          if (current_txn_error_count > 10) {
-                                  nhlog::ui()->debug("failing txn id '{}'", txn_id);
-                                  cache::client()->removePendingStatus(room_id_, txn_id);
-                                  current_txn_error_count = 0;
-                          }
-                  }
-                  QTimer::singleShot(1000, this, [this]() {
-                          nhlog::ui()->debug("timeout");
-                          this->current_txn = "";
-                          emit processPending();
-                  });
-          },
-          Qt::QueuedConnection);
-
-        connect(
-          this,
-          &EventStore::messageSent,
-          this,
-          [this](std::string txn_id, std::string event_id) {
-                  nhlog::ui()->debug("sent {}", txn_id);
-
-                  http::client()->read_event(
-                    room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
-                            if (err) {
-                                    nhlog::net()->warn(
-                                      "failed to read_event ({}, {})", room_id_, event_id);
-                            }
-                    });
-
-                  cache::client()->removePendingStatus(room_id_, txn_id);
-                  this->current_txn             = "";
-                  this->current_txn_error_count = 0;
-                  emit processPending();
-          },
-          Qt::QueuedConnection);
+        connect(this,
+                &EventStore::messageFailed,
+                this,
+                [this](std::string txn_id) {
+                        if (current_txn == txn_id) {
+                                current_txn_error_count++;
+                                if (current_txn_error_count > 10) {
+                                        nhlog::ui()->debug("failing txn id '{}'", txn_id);
+                                        cache::client()->removePendingStatus(room_id_, txn_id);
+                                        current_txn_error_count = 0;
+                                }
+                        }
+                        QTimer::singleShot(1000, this, [this]() {
+                                nhlog::ui()->debug("timeout");
+                                this->current_txn = "";
+                                emit processPending();
+                        });
+                },
+                Qt::QueuedConnection);
+
+        connect(this,
+                &EventStore::messageSent,
+                this,
+                [this](std::string txn_id, std::string event_id) {
+                        nhlog::ui()->debug("sent {}", txn_id);
+
+                        http::client()->read_event(
+                          room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
+                                  if (err) {
+                                          nhlog::net()->warn(
+                                            "failed to read_event ({}, {})", room_id_, event_id);
+                                  }
+                          });
+
+                        cache::client()->removePendingStatus(room_id_, txn_id);
+                        this->current_txn             = "";
+                        this->current_txn_error_count = 0;
+                        emit processPending();
+                },
+                Qt::QueuedConnection);
 }
 
 void
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 570186a..8f0e470 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -204,12 +204,11 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
   , room_id_(room_id)
   , manager_(manager)
 {
-        connect(
-          this,
-          &TimelineModel::redactionFailed,
-          this,
-          [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
-          Qt::QueuedConnection);
+        connect(this,
+                &TimelineModel::redactionFailed,
+                this,
+                [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
+                Qt::QueuedConnection);
 
         connect(this,
                 &TimelineModel::newMessageToSend,
@@ -218,17 +217,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 Qt::QueuedConnection);
         connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
 
-        connect(
-          &events,
-          &EventStore::dataChanged,
-          this,
-          [this](int from, int to) {
-                  nhlog::ui()->debug(
-                    "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
-                  emit dataChanged(index(events.size() - to - 1, 0),
-                                   index(events.size() - from - 1, 0));
-          },
-          Qt::QueuedConnection);
+        connect(&events,
+                &EventStore::dataChanged,
+                this,
+                [this](int from, int to) {
+                        nhlog::ui()->debug("data changed {} to {}",
+                                           events.size() - to - 1,
+                                           events.size() - from - 1);
+                        emit dataChanged(index(events.size() - to - 1, 0),
+                                         index(events.size() - from - 1, 0));
+                },
+                Qt::QueuedConnection);
 
         connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
                 int first = events.size() - to;

From 54db9c89ede00bdcc48a64397cb45073b4fc6625 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sun, 20 Sep 2020 23:04:14 +0200
Subject: [PATCH 50/70] Simplify outbound session setup

Don't send inbound session to self and claim and send all keys at once.
---
 CMakeLists.txt                      |   2 +-
 io.github.NhekoReborn.Nheko.json    |   2 +-
 src/Cache.cpp                       |  38 +++---
 src/ChatPage.cpp                    |  11 +-
 src/Olm.cpp                         |   6 +-
 src/timeline/.TimelineModel.cpp.swn | Bin 0 -> 237568 bytes
 src/timeline/EventStore.cpp         | 148 ++++++++++----------
 src/timeline/TimelineModel.cpp      | 204 +++++++++++++---------------
 src/timeline/TimelineModel.h        |  12 +-
 9 files changed, 209 insertions(+), 214 deletions(-)
 create mode 100644 src/timeline/.TimelineModel.cpp.swn

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 46d83f6..47f0865 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -342,7 +342,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        0665c8baf4af0ce192adb8ca97761b63b681d569
+		GIT_TAG        f84611f129b46746a4b586acaba54fc31a303bc6
 		)
 	FetchContent_MakeAvailable(MatrixClient)
 else()
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index c71a577..da1b5a3 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -146,7 +146,7 @@
       "name": "mtxclient",
       "sources": [
         {
-          "commit": "0665c8baf4af0ce192adb8ca97761b63b681d569",
+          "commit": "f84611f129b46746a4b586acaba54fc31a303bc6",
           "type": "git",
           "url": "https://github.com/Nheko-Reborn/mtxclient.git"
         }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 5302218..07d0181 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -139,24 +139,26 @@ Cache::Cache(const QString &userId, QObject *parent)
   , localUserId_{userId}
 {
         setup();
-        connect(this,
-                &Cache::updateUserCacheFlag,
-                this,
-                [this](const std::string &user_id) {
-                        std::optional cache_ = getUserCache(user_id);
-                        if (cache_.has_value()) {
-                                cache_.value().isUpdated = false;
-                                setUserCache(user_id, cache_.value());
-                        } else {
-                                setUserCache(user_id, UserCache{});
-                        }
-                },
-                Qt::QueuedConnection);
-        connect(this,
-                &Cache::deleteLeftUsers,
-                this,
-                [this](const std::string &user_id) { deleteUserCache(user_id); },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &Cache::updateUserCacheFlag,
+          this,
+          [this](const std::string &user_id) {
+                  std::optional cache_ = getUserCache(user_id);
+                  if (cache_.has_value()) {
+                          cache_.value().isUpdated = false;
+                          setUserCache(user_id, cache_.value());
+                  } else {
+                          setUserCache(user_id, UserCache{});
+                  }
+          },
+          Qt::QueuedConnection);
+        connect(
+          this,
+          &Cache::deleteLeftUsers,
+          this,
+          [this](const std::string &user_id) { deleteUserCache(user_id); },
+          Qt::QueuedConnection);
 }
 
 void
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 31ba38d..704543b 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -606,11 +606,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(
           this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
         connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
-        connect(this,
-                &ChatPage::tryDelayedSyncCb,
-                this,
-                [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
-                Qt::QueuedConnection);
+        connect(
+          this,
+          &ChatPage::tryDelayedSyncCb,
+          this,
+          [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
+          Qt::QueuedConnection);
 
         connect(this,
                 &ChatPage::newSyncResponse,
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 9e99780..4f0c589 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -581,9 +581,11 @@ send_megolm_key_to_device(const std::string &user_id,
                                     ->create_room_key_event(UserId(user_id), pks.ed25519, payload)
                                     .dump();
 
+                  mtx::requests::ClaimKeys claim_keys;
+                  claim_keys.one_time_keys[user_id][device_id] = mtx::crypto::SIGNED_CURVE25519;
+
                   http::client()->claim_keys(
-                    user_id,
-                    {device_id},
+                    claim_keys,
                     [room_key, user_id, device_id, pks](const mtx::responses::ClaimKeys &res,
                                                         mtx::http::RequestErr err) {
                             if (err) {
diff --git a/src/timeline/.TimelineModel.cpp.swn b/src/timeline/.TimelineModel.cpp.swn
new file mode 100644
index 0000000000000000000000000000000000000000..9e96570264bc9cbab5e9b87ccf0e1880e0db3eea
GIT binary patch
literal 237568
zcmeFa34k0&b^kx+G>5q{=5DQRc~)MnBp+Z#(i&NkZDGs0RyH7u&FoC??pSlIr)RWU
zWRb)e$N~R^)7&@aNJ7kENC<>r2m!(wAP`7^5Fqdmkc7ho!twjOs(#b=?2LA0AaqOL
z?dj_3I$oXc)vH(2>#y9g)45>$oC4Q<3WaC?&Fs_-?pq6&KDJP3)+()f*^@VYuUWU*
zX|>ii-9xRu{+{9oxNckPP`ANH?z*ab*sZT?&AZLDUaQ-#xa*p81hdX-SJriE4Yyuv
zy6YzWA3IxBw?1B(pYIz>{xYXPPJsjkx}BM|7o1W!_nZyu<=yEctDO7aZ-2sD?vPU;
zr$A1DoB}xoath=W$SIIhAg93pT?%yOFDU#r*}H?E#WVc-NwNE#{O1Sy_tmlY505=>
zj6IL}&-e20n_}z&qv`}!5$_g2t+UFd&bjJ-eIch`K*#@?Ue
zzt?j5?b!Rf`0uy)_dklg-{8AzIG>8WKg)lw`S?=oz1CZ;N8gUU*K$`n{wVhT-hTL6
zPYQRm4oV-TyVkE$WA8`(@74cg?EORh_ga7V$KKz^f3I|^$KE^sdu>P8#@;{1f3Nv?
zLG1mx{(CK-SH|8y(tof0!=J|9Yq~XmZ;QP@)BoP_@3+L>YrWTY`nlM9jaU1T@5J6~
zzoy@x>UR{Ho>hLlkM-~8#@?^--L?LDvG>~UCVhW@5PPrf%6Be&Irjch{&!dV_pip@
zkNEBy&xBtgHT`G%?@RvuKAzz(!
z0q`gwyw*p+Q^7UB0rvuT0;2L=lP>zE?RL8D
zrjwbdQfYtlP_wnr-06B=dDh*S2)pXel)Lp#De+>MD7^0ce5>7YtI6;yWzThbM3VIM
zJTE8C$@1*dap7q>aqR2VDu>+m(((8nmlN0STC?d^4V}EDBi!3+HNsykC+59wwOr}c
zTFt?t?Rj5LoRh7_wAX1hQ|e>9zDQPk-j^5We62EA9w%FPT~1uPTMKUca+-d9(83Hp
zSWdiGlp6z*RD3Qcrlq=-v|{x8pyfon(``%-(jukSSij4OO+tBGtJd7xt-AvjWY7C@
zzftKrA>b3HK#Txx)Ik9g!T<(+y
z7*&NX*6*@nlPoUPdc97&Tj@BaNOe9}U@BT*s#`esduKh4DWbX5?Jb`@?;>?;(v^Di
z<%;VZEfkLN&vDYi8`SOgh&MHklziAZpiVL`EqimtJr^x@+%4^Pd9hTQX}21C%M05m
z&*C9>al9%&M#miaU3Z(aow?#DkDlvRM>iIZ7OpiR*vYZisg_F6q}H5uR`bD_)2%f-
z#;>#4SwC8knK(0hLw;Qjk>v|!^2SY<3gpgg-?+iinClGws+;<&HARcr#A33q@AaD?
z`y`E`gnQ}5CDp)Od#1K+-+K9$z1ydDZlByT`Gnoui+hYd^p0lLJz^6rYiLquBmlW&
z%}p{RZW@`a&&hjngLN{OBsA`?aC)s-sduZcGg7UU>#bRD9R&9W(($>G(EI(JTHT8}
zsmAt|HFsfWxyk&$9rduQ-iSKvJW|=wz~An6>u{|a_r9}Cc^%nWufg)fzAM+7SJj%;
z)ht
zsN-IDwp%Z^w;!2rGjK7vjeN2bO?IiY)AS6H7Z=y+?lwc`i1XHRWe$&_SUWI$Q=N9X
z)``U1nWG=ChQBS>XIt%BXRZEZQudmf2ln0
z25$n-1s)g!cLOJb_fSip0*c@s;1(*D3s!^AQ`!CsbikS5Yw+RE1(VaN8I>8i`c0;5ng9<4
zfvHE$6Vp&h?U@@KB&Y}itK)uZWu_QPgb{UEVS_hUn=h3v6Yoi$W#H%*%APY_7J1;H
zFps215*a$)6wzAxr6(g@m&sy4b?H>;
z=f_#Frs7IVXe0huf&d!T?OdWF<2()m?sX5hgo2W}TJvy?o}<$`;q+{W8Ym6LMtoxF
zI&>4+VW<%qt+Z(M?)PpZBgyefqFdE#lB3SLb
z8j#-cFICRuvo+&1Tb(%>4=Quz<}B55y4x||J2S0%y|rKi3ynCIbV#vUnK5Wg$sB7W
zUaR3c^X=9Q0tV{fOeg_CPl6x8-+u=@70CMizrx>7fm6V<
z;O{R0e+ggjf`5mn|3h$R@D_OabwJkGuL2VO+i;cn|NN4K{+G;P0iN
z4ITmh2_F6$a5lIVImL6qYVdDJ(L_h}@!+RO=H3fL=Xf3X5CYO?fYZP)5Ny2){1JE>
zxHq^nh+Im>py)H#wU@ovE{fXZx$yk+4{cSYZH+%!rIRk|S7oDFS^>!g`1O5a)=*b^
zQ9dnBrtC%pDHK@6Z@Dyny~kJaB8&I(5!KC*Q1zkp!}xt^GMNSataMVFOf-Y0bEOvk
zv(iJ07%DwI1p&!`emnrp;$$@C5p#@}ky*s(EuNJMveiOrx`>*!rmLGQW2l=Xjh@B$
z)1;}J_*1u7^pVG5(h~WQhpcZ`+MiuijM62KBdVm+uMXw3`qiRUzxEq1D}7$NJMOJ)
zGv5q#3`{r7r5$6qQ;`8hI~wUY
zf(1X%nVcxd&d+!VN6q^GK@$He$-sRI1*txA3IG32@Otn^;Qs^Xfq%f?Yr(7a8|yv)
zn^PdCKu&?20yzb83gi^XDUeejr$A1DoC3db6bP0q_e|}-V*kGF+lppuisejK#LezV
zKod?lY!$hPFi9@ufXiK!JgiS~+Uzvl1^=1d)4^tjY#l&R_!G8RMGF#>TpB}oCy9HQ
z7>vxKT#+qn>@Q%KiQ7h}zxHI;ZrU!caYx6i-Ntg4QUkTp03hoGQM)2`Wunv3-odD4dxE!1benQcF0elD?
z0z1H^;I7~`2pqP6QSeCcaPTnjARsyfe-5^TFO%dagGF!__-_P~?*VTEZvi)hKLHnl
z&EN|Z+IxWq)`R~*pz=@PW8m+>N5GrG2Cxo%m;8Sl{0I015PIAfx;z#9F8Cd=8~i;*
z_F-@Xkp2Ak17b_=Um6wd$yYUTm7bLEYLZ;dvb+oD%VwGzgQUwD5k@zFiIt
zprtf;p{cB+XVUcch-1)lBdzp8J<%yG&Rnwpj#h2z20eZVsakbvOe#A4=%t-TeY;mF&%4zkE%9g}My&9+
ze&pIj(yBCxlrBah2Y;^6!Lb=^6j?9$NTSIfRh2C+wYKKv$c=XtqbM)9~iY~SE
za&EgFRuY}-bWn}2n7xA%a1cC#i)Ki-LHNrt+uLo&k0MbUBy6)*
z;TvKJ&k$Bqp?dAH*T?x4LMAferWIl>&+)o_xK*plK3}C@@Jn4n(GOIBy63S}s$n*!
z+^jG%tevQNTQJLU*tOd#*+?5wp%(fUTlK2hb?Qu@QONF7=ep~ho*N^|+6|-5>ebGw
zpcWB$yW?HOcEsXHgWVCUR~LHOa2u#ns#gh)REd6M(S%Kv6WTBu8~RAYk&79J+1?ta
zne+#YbmwJ$A<7Tl<&mO|ZA?j5!(r;R0PUcKbTmK1M35qpJx*p>0LC=rbg6oBlqc|xn}YjmSu
z&6XZ1N7R}J`hIy;rjU{)-$`o24)m*!X2cNZ4NVo6TB$|YFZo(iv;7R#>`afgbo;Vd
z60-=8I&Z4ubUdoR6cO!b=RUXM?ZY;JyM4Y@!OTLk2T&TBYAm1OkmQZooX!UQDViqR
z1g1Kk(jgh1w3A_*300&myG4qzcEV6r(DcRcOSDu|V9ADNa%tYhxbTkjZN`*LsG|sx
zYo^t9isoy^za9hcAuWI}TkCAtC|e9But>4dS-ZB@kx@9&``g|%+_7d)F{En^cl)-B
z7ybW@@Y4odh|kOW
zJx67RSS!o8;{K+)v&E5Si;c?eTEn7XiojzdZWo&mGBNi>1|`IdBN&LY0))`yU851+
z1ctg@g9E5eahi3F#lLZX5|1cseOP#=AEhP+JzZF(s+h2ogtW6_Hf#f|3j2_)oZx_;
zx-k(FL=$q*DdFgilglZ|r5IfBBt?Z@QtGWagf-1l=^8AYAZ@6I%zd(dd3SlS-YQoe
zl)L9JWKlO8nvtT|oH`9_+*#za(=I0Tu$2;oofJU~A$kx45w*$`+jcse;6o=ws*8yT
zT)W;>g@eGu)>t$*DR=W3c0)YYVB76hJDYS^PTplePX`l
zGS|g$`2?hr`&fKs1u1Z~?wV}6RY_g3psQKJ#)=eEE|JwLYbz2{xt5k*I>y4z#l6)9
z*X~=j%jjY_vRO5zTc(Y1@l_*fwatiJ(naWEA>fKur#6GFQ?Vpl98j);0!pBXo()f
zmVns=7aFo{r&}t~eNUzNOLNmBJi_0)M=GvaC6g^@OyOiZ*K)q4-!tN1`fhW{Zan(d
zh})Sw%lq(nt8oT*S(5n;8%e9v(NR>15vu_8Ms=ElSY+NADm6x;2$eGvF#-@ImsIX>RDrRNOh1xLwh2
zoH{3Mwvqg_uS;6E8wQ%D_Y2lCC^Btac2GK#o#NP><%g!T-F}H5;GHad9%s%B!KYCY
zD6XD_DHP-6#vF91l$dh&iy^+sZulOxsC7JA^e>V*9qKt^4<0x!oi?$tq-r6clPyo?zq;<1@9k*Ex8gm)e8Q<{{DF2CqleR$5ZkSp+OdHo^FvH4Ft5F~Q?tOUW
zexK>P5SxzjLTzS|m41=@iPXmtL7L^w3+jLHdxBFpGNmB`ip@YG&0i`{d>Se1|Idd{
zf4K1O@c&|T(=Ok2?uMP=?3<%2$b(BTf91Ofr<@h40-_O%-w^1J_9OzQm~`
zvgB^Vmor+#Z2f*4TQB_@klsTJ3?ct;x@_^5&E0IcX&2XsrM2yc!*tN^llOjS&IEZ+
zeiwH!;h2rPDH{1p3kDL!a*jC>rx1@upM`Wnux$togm@Bjb}Z_1oV`gRU@Y!2fBT7w
z_ejX%%h1_GgFGRzLtt=H4^S6Y`JrXz+Q>Jor$9enHrTgQb!z@!M`KY-QP=
z#ds9?&2Fr5oY*VD+fBMv-zo{=boSX1l^i8{xc2$=HdgcOj)aYc@X`%S==RcJ^e$zV
zQX<0tS6K_XR8}S6|IhJ1e>1%PG`Kss8U9{${qF+a0bf52?g?&%f1d&O1AmPS;Tmu%
z_#o^1N5D4lBi8q24}Tfl6}*lF?gMfjz#GASFbUR!)!>2Pd#po!6MO@F9efC^24{eu
zvQqLH@M5q44g*>99|89Q-zOj61AhV}Poe`Lx&XI=_knq^5u6JClYD;{+yp$Z9h?F_
z%9_9K@887lyMupY-TzbIli=y#I&c&`9$X3bgGpeoptH*RP3>p%ep8#G-lf;m-+B*v
zLTRwjEZGZcvlp!=T2y4+P~Cd_{dxaT4O{QS#H-)vJu7|sUhi4{7;@wLy)@0?Rb~-G
z(n3w%v`f>Vjm{-g4>4ZBZZy%ak`9y(srLyTg}KDW3LT0GX2d!L(-jTRZ=&H7ZX}9T
zmd=5HGaeYn*l>{1+sh^{MhZI3-tw+yrJ!-^T}R|AIU=Vl14pnjBWVeZAq*hvWp{Ph
ziCtpBkP{5d!NI~zSV(hp3t|w1HMkr2j{8e&JELlr{k^K8Lx`Q#CQVpQkYP~fhE}Pg
zAl;&rPPl+YZBGlBF4tUA=nNmmA47_{iu|QpH*Q+0=kl!Is@oo)awak%vqYwWgyyk9
z&pZI>6Q`jp{dFU4BG?#bf4$r86b+3JY()G5)?jH$e;ztOa*Q!LEA
z?17zZ&DSbR1={mCXsBWqV~{|_-#}ry^Yz8a){;S5Z-a!m!;?()4h~o<zx11DUeejr$A1DoB}xoath=W$SIIhAg4f1ft&)j
zD+N?wInY7vW|>UZVPsL@l)|9j4(hLkCG}z~4oi%%Sy%Zjmxh|TmXMhxj6Se(a4FVt
z_s?%M8$g3R*|24`M3)3LK3OecA>QPDmG#z3gSG#^iA9vR%R&nLzZ|b9*A4LfPX@B?
zFZ=$#!~)J|zjDp@??5W9ndN+p$EnoNqgQ5_x5<>b5ZPk$&=}P);Z+OVCPD
zp_sT!`%~(voA^_=^LkXv^GgL0N#Y8W?{ezFa=y*$P*X@?8g|}h?AS{u!1c@PBaXjL
zYJU~(!2f*GG7yK|CV7tgdjf8%6~O>%E^VpDGq0F*kEm7i;U*T0_!$!m
zP0sOj;|4ss*=i5(%?Le65wZMdDgUbKr6v!!%Qy`~6PJx^PIR$bGES9y2;{>y2%OFaY3fKg%^ut2~q_z
za1!QHH}RL2<}|mJ&c}(0Lun8e`f7f|ZjXc#aVGLi+=SNRZ?&NmTdg_xz99S7>+B%i
z)L{)t_I5av6XwL!xbGlWzG#PC;$od&EP2Wf%ea|O*@~VxOf%R^=vO0mTnzNdc@Pb^
zF^#dp7c=i*?_zI50|EH;5Vt$8+I&NvIMb4?c;($_#tP86wi!lwrN1=MNFk)(G0
ze-{4#iy|0+|CjY_x&9Yk|M$St!8KqfxG(rA{Qir@t^hm0-;&+;gM&b1
z0vkaINF1jDJ^Sy!k?T!?gWz=VB}gr{1Kth(3e>?NunX)2mxD`y=oTCXd%$DB1>pYR
ze&8+W2mBs58~hl3gLi;ga2j|JkaGlH3Z}tsunYX0Vv@52s^G!khX~aE790hS0B@zZ
zpAQy50~En(a3;79xCgj};yeee2M+}g2B(2Lf}c~o*Mbk>@1)=e*8#d!GGw$1wBCwz
zhCm|P3Q}TZ!e}c%WOJ!oSgS$JOp6^^YR4r|?dcN{7q!t~+Lqxt%ENB2m!aWaUho!k(KX
zTiyE7(Ch^@>J>6P`5k}~lF~HS7=NYF-L(ICAB;4*)P6HWjFb~M?FrRVG!mK=k6aR?
zxJkOzEn9f5Z!lNfZ+9j~{uTnSOZ_qjlr-H1(Hb&+rQK;v1ZR^lA%!HEhG#BylLV{V
zQZYEv-PlmEEh6@C#5~MELq@PoJv$OnV(DNyrPW}>VFSDWYbttPdocJUL-J2?H(U2t
zo8ZJ$K+>OeF}2zr#+>TMycg@|-c-5XaobHcY#(+v1vSnr^#@8?6C7KVayGL>Hh555
zn%)A(Isfn2i4i55Dmq=b>dLfyQTw>&c6O6j`
z&bTda(#tIgry{ghE3b;$x1$HH<=dFbEDqSLx-EBv7=-|7*KZPoL9vl?ceOvTmHzSO(3TEh$udX~g9Gcy-6xSr~w
zjymcV8Hw;0CyQ=1z|MGG|-p!FJvV>Cum+M8*1gGUaN1MwLN@;&LU5IiQD~{6(y%EWbo$*cPhE
zTwX2*h?`JN-Qp!>bC;eknsQopZ4sTSbP0c(<`$Ij@ZqI>J7^q(`lII!qVe+v(S%>r
zf*j%b0lu5`A?DKXm#%)gjW%|NDnEOeyjJ?{7Asowv-+e
znlc_#%FT;=eP>!JDq3g3B*l%0;vdQ|G&7rOI)e$%4rJ!FZ=&x55VbQ
z6>z{q!E531Uk#oK9u6J~9s+(s-FYhbUGOL{0`3a#0wny8lY-}gyz&|Ppa#TV;J09deji)}zDE6i7kD{%84$j0EjSyj0mAb?l@afEft(F^Ik*fw
z00^J|4{(%!3*H3Y0A3Fkz>VOy3I959G1xX)^X;FS7Dvh>y*9p$(Xw!_9pXS^m22&W
zV1(+|sQeMxKbm%}v!+%(A{NSmuccz*7|f>4G)%npDu1my*)nrNJVu$Y@c7;*g*J}y
znzuc3Dip-w6UnJnU8EyC4Ya>
z_IBIJ3_9L~(nLGy5|5++r$YIIpp>HJ)P``Qtejw{VW72F7!&Zs8oCJwT5A
zSw!2F^_*&mkMdoNfQ&v`>q@x6k8EljS=$yW+lmL$vK0)7{mA99Od3oFMGt$djl?
z7oKr-^TLc8v=cdAO#~Z$wXyt^HW}?WWW3fUqi%gNNnvCdjE~n8A-h(J5Fw})%%*$v
z^ec&Ymi35a)fkiYv?gbSmz$ncKC`^$CvdN=0y{WfkP2n018+8SzM##kbbd0;mX1s=
z$*Z`@(zUwDEZYyx35S>Fw$gD78eUpj(%c3OZ>4E)ylD~IOV=D`+zJ|)-u*sT%2b`q
z;AtjvLBq6yN|^yr0n9tk9MhN>+MN-RB*7ykc*c~m$|Bcr8P&4DC)Bgz+>k8Mlro7
zr?kq*DO8b3s^nJaELWo7CFpQ)I{Olh3)qIt{0W&OWaL>ZxK@B7Zc^~!3Sjal`u|^{
zUOqrVp*m^p5dHsWfoFmX!2^NugSY(3DUeejr$A1DoB}xoath=W$SIIhAg4f1ft&&-
zoB}E&NY($sFj=#MkvBFR1cIBM&z>l!=YoP&x#C)RbMWn;{?g(IhiC;tfi{{j-zG9Hx`S^fX#vi5!t
zd4*R|^^j`}et!hWTEFP~ZwB`R_XekcdxDRUnFHVnU^O@coDO8o|2aq={urzU_W*YT
zC(Cc*xdZpdf;FH3evaEW!8^gLfUJK$3_KLv8GM@by$6V%Kn2KdBe6614e)Mo6F3SU
z4Sqnj-wZ0?f#43{i;(9F;CiqZIN%bhi8k_`Pjrisg
zAUXn{2G@ee1JMbPJpgY(w7Umv247|c|4+dy!OOwRKpT7-eDS0`mvVZ_CRUVLg1Kn1
z<8EoU%Zn322eXSRygC#T!5s@pv$Y9`bZPB`)kwd%-dbSIMwTM2J~qmvp--q$O^!-K
z+fw{^W6o;hc^`HI-BKwLgq2*4IllU|(Y~=N`&P;+fv4zErzKJon=V6ayOkPAs_sR{
zdz*V014k)V8jC26`m(g3Rcp9@5+j;G5}L{1AQl#{?Q*KighL!@SrQs2ms=y$GwU|n
zc7_ukd=V#nuZ=tGO-L&Xqax^5MQJit3M7wBKk2HIM!kyjMZ+baQTL)$hxCY%X7==Q
zja?v(ezhu}?rc@vda0D~IB-2%LaVJwP3V)5CTNjOGDhia%&C{XPKdhtaYT)^
z>P~lOiz6mLRJ9Qncbu7atHFYlY!f?rjFr0n!DZ``Nc_&)&0#GeO@rR8dR)4nV~%Jf
zrF0bfu?hd|&CaweI!}piQnG=oUmMYYp5+L_AW#-(pu}23mShC7uww|uF^aZHj$19M
zwEjQ>HmMF4e0nNX5=ct$d|}fb3!VgZT7m!mtut0%VpUL5C9&JH7dtIUc|i%M_dT6b
zX;0Vfy49_AG(ZO@rE(3k#mH=5u;Yy?Gx}r})v_^XeOPTk)1h~>o+9VXF*G!1MK?-z
z8ze$AC1=74dQ}ag&plF-p+u$RI3<)=mlt^~>eH1qcVTC_iMhA7v!-ExFsSciK^aAI
zf98h6uIXzSd!04&DwebMlEH-%6GP9mr9jZy>5^HMN3C3;dV2p
z^5<5zsmX%BkW*EgFX5RPy>ZjMpME0E8mp2}$r>U}GUJi79z8Z@M-XV0$jEZCAT=SJ
zg)J#r$TYNSG6lP4LZCk}m(+c!BND1w-zK9WeFv0ed^_b*45iZLt%}*Oqh{xvVA>G(
zDjy{6R-2+pnUQP2X18vy-W0SN2GOXCUkaHSws!J^-HF2)OI1p(vV`iT<7n^KP!`=~
zH)xO)V=zN#56c)S58bgX<)LLHeSua~)f_q_<=au*v_0#$8&do|3OCK2h9~R)AEP2X
zK{^LIBvH$h>+MvzCxP?8oxxl9@M+*ExI_X0w-MP#!6LW}+!K6+R`WODMo=j$zJ*nixv$Q1S*|gchNSmBSC-o~EMM&`&pQa&WD3nJ
z(oCUcHY(S{frwFL3LOuoQbrEBt=pI{AsLeoSjsUTN4U$S@#tNdS3g?S#AYuFVzzyq
zs?W|&ZieqvIUSrno{KiRTW
zQ<28NRI8NP1jYtCPiWs;7Ak59Mc1H1q%-BUBl?w604_4M@+XT<3&05s^VrOWV(R(P
zACVQnUXrtf4m}ST1Sn__v3rKmfrNLzuW;4UjZJIVn1BXsYE;}`Z&^T03&^ekfxn<<
z`bH&OyrM-yrJhDEKh&ivt?>W9k1*nC(jmhCU%*YSHxlWMKy3bR0H2}L{4{tu*bX*=
z2ZB$+^S=u01!sYOf!}{Cm;;-^cj%>W1xLUxa0>W3JpXG!1B`+Dfd8g_{1=e@|1SkD
zH~=06zD>q&1ykUEsmiYcj{^?`|4E1N&)|#TMsP8>5BNL-%4@(y;1uvbfh9ZUny#&IJzxpJEhx
zH+UKFfUNzC{XhST6cZ1oQX~h}l)A2cnLh|>K#{>s9reEQ1DS@b_W@%teaE1KmWHkO
zVd5BUfYOlTcfSWS{e!;C-4J=o3~Gnh>o>Jmr#+iqJ&jiLY!4p9-=N=)#9C1H$l=$Q
zxQN*7`9|F&SasE?Q%z^+%goTq86o*5>dIAP2A7?=W{QFNKG|B{k>_1(Fl}S)%Z{_*
z{pe;0(1Df-WkFLBzh|UsA8U;1`7^0n72f~P7{zFFVEsX{Lp0JD89Fj!#v>V$$?U{^>$6`1)|Re1X5ED**j9F_0o7$tLW7rV`%6k@09ox~FYmBh87-`#mB?rl
z=7Yw0X2ddXjE?5gko0bXiPlfJyx7W~!~hE=EE}VLBo0|#7$}a|R3&qBcWlMXUESDR7gNPKE4jC^NPvQVpHy1-D&FRFMg)Ibf{*fl%fYKtM5a{W@1
z2`x9V3@{9rd~LVhYR(FdCQ}ktzB2J%CCRjG(XbUH5mAKriJ1IcnT8^bZkRk}teQ74
z8`)3Xu#@-4FK+NHUd(k-W`TjNig{-_32z^YSw-3MY*d|W35OYGLs#)A?~jl7&&aPt
z+HXE5k;?lOjdZ%+ZO@^ko5fmRj@nMa5Xhn0%%ZO{Q1t7U>s8A0?nQMBxYTPNbHqzKje0R+eW1U-Jv1H7hIOm>^mkTFnWd>2PH)f%&o*RE{7dZclrQd+kTP06rhO-}N}
znU_W(c3s#Ss3vK^z*nDs-lI2~KZ8V)=xUQ=h{mt?XgcVWqYi48#|V!u_ATH*Vd4M3
z3jck*%;VwzS8mQD!Ml(J`~|3h_2A*)
zq2M9lTgV2!i#*`l-~-^LKLVVLSijVF@%yo*
zG=BAiAh~CsogJLaKKwRbLHT^111&o6pr)ORL&S8n22I8H4O6ccKTM0eotbmb@zu>z
zRbxeeHb{{MWbVv-HX5s#R6tW)R5WvmI+?hMDKK@5e96O}D^XBuwKsK%U?P$X8tAoB
z6_BLvuXyK)ePmhFpr)EgXbI+0H;F{uL|sekj?@{s)J^=Uo9MR5H&Vwmf$A1cbRm&y
zUz^b+kc4K8&BoS}KN%P<^|XsfswDN|7EYBW$~2lC-Y&R5Q|>xRl>SQm$6Jbi7q7MH
zYK6Vz>t`l9nL$|^*p{kAuW<>5wdmDN1JL^t@#_d5)csz}!}ktZakknuc129FB>-z~
zQyniWQYMXTVqtO2^g;BdrgSuCl3Q^LQ@fy1>OG#_YRzMmUA#gTi!$m@Qa}(H^9?=3
z9*%@zQzo@I-eZbeIAxk3y~h(aL}FrPBWVdQL&a0VsFIozZ=uHS2F$RrUMjthv}tX>
z@{L^TCOJ{Jf@VP`xOSr=BolT^>`eB|;{~Zq-PRcLm#{b_%J;{omTET|*c>4;@Hz#?
zJ)^)bRAG)_E_IV!shcQ0Ywl%6DVMs5KXu!VyY>@G5p@ewX*d2fJoTq;VSmcyn}ljb
z(k@jJsufAyl4`$UYhYS7e)TexTw{8ZF|*D*O2>|vUix+QYJ_5(&1F@DDM#bo8VoZ(
zcf@S!j;VdQ$yRK^EyZFqi$}7)t&PnFzJrw{zrqJ$#f|9>QerYLg-ho&p5tScx`{s}
zq7(9`rK4`)(isi;({I%+9Nq@}-AxI)XKMcy`}b|%R`fSkSUX!?$RsvrNUb}H{p1ragYVBM7wUc7R*f0Z`+~0
zWzK4!Rxul=5=FR$3c><-hztp+k1-nGjdIQFidyixiUGb#dDJsukpD^7o#$_SUg5JhDRgI
zi@merEM8ivSM04yo_Mc9s+bQI5aC0_IwC7d@gakq??ZmZe5mLJA1bAZ`3Q?h@YNHm
zAjXLK#03vXK4R{&mBdVk`F
zr$A1DoB}xoath=W$SIIhAg4f1ft&(41%3l5pz=G%IVya%&tbYgAs5VxRv!W(d)kpxSmLlfDzCl0vCJ*>G3~;-vOUQdMK+N
z+dv6?4I#w2U=-YhZ2zU;-r&yQSx6+G3gleAr`-YBCpZcG2oe6Pz>i7$>$rayycg8K
z&rFVDe}dvL>lb!W#Y*ITvll6*DnwA}pU3U29yO~rA|Vx=z_WD<13%16`wt6x6a?=Ce^4C+s3r|iZp;sds%(R}tpwW;tFUM^wArVqQ?hW(IwcFoEDVISd~wU3
zwl3Au`qe0tpv*ROdzGW+QR~HtE=S@2r{VLj2g3iuE}Ki99}BklcRBm-XUv^n33h-p
zz=!ELj)GO-uaN=Bp8p9j0#<>qA_w?a@H^m4UnX!6F7RsbAn*wofq8Ima4+yK_>wEZ
z50MG{06ZIP0RKN7)w94Q;2z-1$OXOxUJfn?KSC~W8~7lY1$P0r(BbU^KZTHg0d4^2
zgKv|;XM?@qRImpmFQxk6ciA^tYj*qCukII?JH9Yi=KRc%wqQT+@pmKkBy}-zjd~aR
zw5!IHzH`W3bguDQO?0NQ0$zxJw)C@aXuQ&1obR;cl>D@Aua;Ld
zi7B~9Dy}(H8Z)h;5S#Md5>836#S$NcUN5htG$6@I5i`l
zXM|e7J_ii0H_CNUqjRgKsTjKeHi~8TeoaZ;+6jBkLLr-iX>X*9;7}7=TV^nDU$jLl
zJF3I4I9~z^4A)Ymg(So8)yMQGut70Q?YG^w+30N&?&lF>5q|Y8jW}m(PP8NgmG?A~
zMBPFWXv)O{5d@8H1>*?O+_pNu{~nj-JXCuLCEO3xSPI)_%9}%YaMZAS?}zFmLJ^1x
zDgB`}F(=)1n;~h?wM|U;6;ArOeswK9xIV5n9fWVpuaJ!?Sna-1V3%ap
zZMto=vN1`5V!E_+e}e~vshVEMo5OY?9TX;CFqJUdsy8^t$Maa-8!x1jM_dV-4J7mO
zp*fwz(3wiWB(EH_IET$ebI##%oi{o%+WrrJMA)wo%;?&7%>hW5(BE1w*BU}ory{>h
zpVhZX3^HWcHOqdb6i}ekE{ymy>>!eyWW)D
zxH=d~5^@`y{8{6c
z?PmOH=FPKD%%z@j22Fd>)U(xe8e?|lb*|HyFO~M%%DBDVb~1xKaL9G%>2fyv-9_uL
zTeMD`n`E2U)O>ld-YPQ(?Hp-J$H|^KZ+6^p|2*2ma}K6W8)*$mQrfgt>NO^w#nE_?
z#9C31JHoQtVqrrga<-^EZu#MfYy!495{JVxEOkUCw1#I*i};sI`2R1!i(f4D9sXbB
z4|2T@e%}N41aF4d|1)qExDtE^e*ZmS3Oos%4!#Dz|Jz_I_yJ*k0o(u%0TZ?jHvgMb
zAg4f1ft&(41#$}H6v!!%Q{Xr$5T7^qdlo*0jo4xmeYDWe;|jm)8}E{~k|LXer(j}a
z3-A=CAjDqUc4I^hsbSBa*;!XN&q8r{({y_x%}|+UZ4j>A`Zdw@qQ#aBb|!5Sj2*yH
zIpcExnUfH~|4i0=y{nK|6eY2JY};yL^*%sSY!v1U(Ook(`~efVFo(<`C5GECCQ>%d}9SZP3mPfJm>(u~)zmNnm5M7W$H
z)ibGxen4?&B&X+ZDw;6iK4A*`=X<)j2~&+0;0)))=*$
z7+JqkUapu2gboi@gw&CUkY9VUI5H@
z{x|t8r$A1DoB}xoath=W$SIIhAg4f1ft&(41$+vKWjRdZyE`#UT}Pp@<}PeoX*buX
zOS;v)<%OO0RGgkkP;@c-n0-VMIxQ{79UjbN;Xyz&_GQ&st|CyTo`qs_t>-U_*~TBr
z-R3&ns==2AlB%8!O-?+iw`Sp6Wo=u;AQHOrtjZo6v4-_2PMe*kyWrSo)DMz`i683e
z9v03R=C-#(CN6LZX)U3jLJUR;pl51V&OPF^(mECn{X-hpxW0z)d4w!G%rwXGkTnl+
zy%`ZUt#SillvT+iX1k@qs8FJR*JaN$^e7;>pwXxINQEpM9UFfXX-6E{t>XebL07GCft;lS

`E(A&g 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 F{|AftumJ!7 literal 0 HcmV?d00001 diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 66a6d79..af1f7b2 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -32,38 +32,40 @@ EventStore::EventStore(std::string room_id, QObject *) this->last = range->last; } - connect(this, - &EventStore::eventFetched, - this, - [this](std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline) { - cache::client()->storeEvent(room_id_, id, {timeline}); - - if (!relatedTo.empty()) { - auto idx = idToIndex(relatedTo); - if (idx) - emit dataChanged(*idx, *idx); - } - }, - Qt::QueuedConnection); - - connect(this, - &EventStore::oldMessagesRetrieved, - this, - [this](const mtx::responses::Messages &res) { - uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); - if (newFirst == first) - fetchMore(); - else { - emit beginInsertRows(toExternalIdx(newFirst), - toExternalIdx(this->first - 1)); - this->first = newFirst; - emit endInsertRows(); - emit fetchedMore(); - } - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::eventFetched, + this, + [this](std::string id, + std::string relatedTo, + mtx::events::collections::TimelineEvents timeline) { + cache::client()->storeEvent(room_id_, id, {timeline}); + + if (!relatedTo.empty()) { + auto idx = idToIndex(relatedTo); + if (idx) + emit dataChanged(*idx, *idx); + } + }, + Qt::QueuedConnection); + + connect( + this, + &EventStore::oldMessagesRetrieved, + this, + [this](const mtx::responses::Messages &res) { + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); + if (newFirst == first) + fetchMore(); + else { + emit beginInsertRows(toExternalIdx(newFirst), + toExternalIdx(this->first - 1)); + this->first = newFirst; + emit endInsertRows(); + emit fetchedMore(); + } + }, + Qt::QueuedConnection); connect(this, &EventStore::processPending, this, [this]() { if (!current_txn.empty()) { @@ -128,46 +130,48 @@ EventStore::EventStore(std::string room_id, QObject *) event->data); }); - connect(this, - &EventStore::messageFailed, - this, - [this](std::string txn_id) { - if (current_txn == txn_id) { - current_txn_error_count++; - if (current_txn_error_count > 10) { - nhlog::ui()->debug("failing txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - current_txn_error_count = 0; - } - } - QTimer::singleShot(1000, this, [this]() { - nhlog::ui()->debug("timeout"); - this->current_txn = ""; - emit processPending(); - }); - }, - Qt::QueuedConnection); - - connect(this, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - nhlog::ui()->debug("sent {}", txn_id); - - http::client()->read_event( - room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to read_event ({}, {})", room_id_, event_id); - } - }); - - cache::client()->removePendingStatus(room_id_, txn_id); - this->current_txn = ""; - this->current_txn_error_count = 0; - emit processPending(); - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::messageFailed, + this, + [this](std::string txn_id) { + if (current_txn == txn_id) { + current_txn_error_count++; + if (current_txn_error_count > 10) { + nhlog::ui()->debug("failing txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + current_txn_error_count = 0; + } + } + QTimer::singleShot(1000, this, [this]() { + nhlog::ui()->debug("timeout"); + this->current_txn = ""; + emit processPending(); + }); + }, + Qt::QueuedConnection); + + connect( + this, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + nhlog::ui()->debug("sent {}", txn_id); + + http::client()->read_event( + room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to read_event ({}, {})", room_id_, event_id); + } + }); + + cache::client()->removePendingStatus(room_id_, txn_id); + this->current_txn = ""; + this->current_txn_error_count = 0; + emit processPending(); + }, + Qt::QueuedConnection); } void diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 8f0e470..ddd238b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -204,11 +204,12 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , room_id_(room_id) , manager_(manager) { - connect(this, - &TimelineModel::redactionFailed, - this, - [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, - Qt::QueuedConnection); + connect( + this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); connect(this, &TimelineModel::newMessageToSend, @@ -217,17 +218,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj Qt::QueuedConnection); connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); - connect(&events, - &EventStore::dataChanged, - this, - [this](int from, int to) { - nhlog::ui()->debug("data changed {} to {}", - events.size() - to - 1, - events.size() - from - 1); - emit dataChanged(index(events.size() - to - 1, 0), - index(events.size() - from - 1, 0)); - }, - Qt::QueuedConnection); + connect( + &events, + &EventStore::dataChanged, + this, + [this](int from, int to) { + nhlog::ui()->debug( + "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); + emit dataChanged(index(events.size() - to - 1, 0), + index(events.size() - from - 1, 0)); + }, + Qt::QueuedConnection); connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { int first = events.size() - to; @@ -916,10 +917,20 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: OutboundGroupSessionData session_data; session_data.session_id = session_id; session_data.session_key = session_key; - session_data.message_index = 0; // TODO Update me + session_data.message_index = 0; cache::saveOutboundMegolmSession( room_id, session_data, std::move(outbound_session)); + { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = session_id; + index.sender_key = olm::client()->identity_keys().curve25519; + auto megolm_session = + olm::client()->init_inbound_group_session(session_key); + cache::saveInboundMegolmSession(index, std::move(megolm_session)); + } + const auto members = cache::roomMembers(room_id); nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id); @@ -961,19 +972,23 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: return; } - for (const auto &user : res.device_keys) { - // Mapping from a device_id with valid identity keys to the - // generated room_key event used for sharing the megolm session. - std::map room_key_msgs; - std::map deviceKeys; + mtx::requests::ClaimKeys claim_keys; - room_key_msgs.clear(); - deviceKeys.clear(); + // Mapping from user id to a device_id with valid identity keys to the + // generated room_key event used for sharing the megolm session. + std::map> room_key_msgs; + std::map> deviceKeys; + for (const auto &user : res.device_keys) { for (const auto &dev : user.second) { const auto user_id = ::UserId(dev.second.user_id); const auto device_id = DeviceId(dev.second.device_id); + if (user_id.get() == + http::client()->user_id().to_string() && + device_id.get() == http::client()->device_id()) + continue; + const auto device_keys = dev.second.keys; const auto curveKey = "curve25519:" + device_id.get(); const auto edKey = "ed25519:" + device_id.get(); @@ -1015,42 +1030,25 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: user_id, pks.ed25519, megolm_payload) .dump(); - room_key_msgs.emplace(device_id, room_key); - deviceKeys.emplace(device_id, pks); - } - - std::vector valid_devices; - valid_devices.reserve(room_key_msgs.size()); - for (auto const &d : room_key_msgs) { - valid_devices.push_back(d.first); + room_key_msgs[user_id].emplace(device_id, room_key); + deviceKeys[user_id].emplace(device_id, pks); + claim_keys.one_time_keys[user.first][device_id] = + mtx::crypto::SIGNED_CURVE25519; - nhlog::net()->info("{}", d.first); - nhlog::net()->info(" curve25519 {}", - deviceKeys.at(d.first).curve25519); - nhlog::net()->info(" ed25519 {}", - deviceKeys.at(d.first).ed25519); + nhlog::net()->info("{}", device_id.get()); + nhlog::net()->info(" curve25519 {}", pks.curve25519); + nhlog::net()->info(" ed25519 {}", pks.ed25519); } - - nhlog::net()->info( - "sending claim request for user {} with {} devices", - user.first, - valid_devices.size()); - - http::client()->claim_keys( - user.first, - valid_devices, - std::bind(&TimelineModel::handleClaimedKeys, - this, - keeper, - room_key_msgs, - deviceKeys, - user.first, - std::placeholders::_1, - std::placeholders::_2)); - - // TODO: Wait before sending the next batch of requests. - std::this_thread::sleep_for(std::chrono::milliseconds(500)); } + + http::client()->claim_keys(claim_keys, + std::bind(&TimelineModel::handleClaimedKeys, + this, + keeper, + room_key_msgs, + deviceKeys, + std::placeholders::_1, + std::placeholders::_2)); }); // TODO: Let the user know about the errors. @@ -1068,12 +1066,12 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: } void -TimelineModel::handleClaimedKeys(std::shared_ptr keeper, - const std::map &room_keys, - const std::map &pks, - const std::string &user_id, - const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr err) +TimelineModel::handleClaimedKeys( + std::shared_ptr keeper, + const std::map> &room_keys, + const std::map> &pks, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("claim keys error: {} {} {}", @@ -1083,65 +1081,53 @@ TimelineModel::handleClaimedKeys(std::shared_ptr keeper, return; } - nhlog::net()->debug("claimed keys for {}", user_id); - - if (res.one_time_keys.size() == 0) { - nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); - return; - } - - if (res.one_time_keys.find(user_id) == res.one_time_keys.end()) { - nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); - return; - } - - auto retrieved_devices = res.one_time_keys.at(user_id); - // Payload with all the to_device message to be sent. - json body; - body["messages"][user_id] = json::object(); + nlohmann::json body; - for (const auto &rd : retrieved_devices) { - const auto device_id = rd.first; - nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); + for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { + nhlog::net()->debug("claimed keys for {}", user_id); + if (retrieved_devices.size() == 0) { + nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); + return; + } - // TODO: Verify signatures - auto otk = rd.second.begin()->at("key"); + for (const auto &rd : retrieved_devices) { + const auto device_id = rd.first; - if (pks.find(device_id) == pks.end()) { - nhlog::net()->critical("couldn't find public key for device: {}", - device_id); - continue; - } + nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); - auto id_key = pks.at(device_id).curve25519; - auto s = olm::client()->create_outbound_session(id_key, otk); + // TODO: Verify signatures + auto otk = rd.second.begin()->at("key"); - if (room_keys.find(device_id) == room_keys.end()) { - nhlog::net()->critical("couldn't find m.room_key for device: {}", - device_id); - continue; - } + auto id_key = pks.at(user_id).at(device_id).curve25519; + auto s = olm::client()->create_outbound_session(id_key, otk); + + auto device_msg = olm::client()->create_olm_encrypted_content( + s.get(), + room_keys.at(user_id).at(device_id), + pks.at(user_id).at(device_id).curve25519); - auto device_msg = olm::client()->create_olm_encrypted_content( - s.get(), room_keys.at(device_id), pks.at(device_id).curve25519); + try { + cache::saveOlmSession(id_key, std::move(s)); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", + e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical( + "failed to pickle outbound olm session: {}", e.what()); + } - try { - cache::saveOlmSession(id_key, std::move(s)); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to pickle outbound olm session: {}", - e.what()); + body["messages"][user_id][device_id] = device_msg; } - body["messages"][user_id][device_id] = device_msg; + nhlog::net()->info("send_to_device: {}", user_id); } - nhlog::net()->info("send_to_device: {}", user_id); - http::client()->send_to_device( - "m.room.encrypted", body, [keeper](mtx::http::RequestErr err) { + mtx::events::to_string(mtx::events::EventType::RoomEncrypted), + http::client()->generate_txn_id(), + body, + [keeper](mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to send " "send_to_device " diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 390fa1e..61d00df 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -285,12 +285,12 @@ signals: private: template void sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::EventType eventType); - void handleClaimedKeys(std::shared_ptr keeper, - const std::map &room_key, - const std::map &pks, - const std::string &user_id, - const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr err); + void handleClaimedKeys( + std::shared_ptr keeper, + const std::map> &room_keys, + const std::map> &pks, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err); void readEvent(const std::string &id); void setPaginationInProgress(const bool paginationInProgress); From 94690ebd4c22c8928b92c4f1723d1c6c5b798698 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 01:14:42 +0200 Subject: [PATCH 51/70] Clean up verification and key cache a bit --- resources/qml/UserProfile.qml | 2 +- src/Cache.cpp | 331 ++++++++++++++++++---------- src/Cache.h | 30 ++- src/CacheCryptoStructs.h | 50 ++--- src/Cache_p.h | 34 +-- src/ChatPage.cpp | 56 +++-- src/ChatPage.h | 6 +- src/DeviceVerificationFlow.cpp | 126 +++++------ src/DeviceVerificationFlow.h | 12 +- src/timeline/.TimelineModel.cpp.swn | Bin 237568 -> 0 bytes src/ui/UserProfile.cpp | 70 +++--- 11 files changed, 399 insertions(+), 318 deletions(-) delete mode 100644 src/timeline/.TimelineModel.cpp.swn diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 1ca9dcc..dc6bc16 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -136,7 +136,7 @@ ApplicationWindow{ model: profile.deviceList delegate: RowLayout{ - width: parent.width + width: devicelist.width spacing: 4 ColumnLayout{ diff --git a/src/Cache.cpp b/src/Cache.cpp index 667506c..8b47c35 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -91,6 +91,7 @@ Q_DECLARE_METATYPE(RoomMember) Q_DECLARE_METATYPE(mtx::responses::Timeline) Q_DECLARE_METATYPE(RoomSearchResult) Q_DECLARE_METATYPE(RoomInfo) +Q_DECLARE_METATYPE(mtx::responses::QueryKeys) namespace { std::unique_ptr instance_ = nullptr; @@ -155,26 +156,7 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); - connect( - this, - &Cache::updateUserCacheFlag, - this, - [this](const std::string &user_id) { - std::optional cache_ = getUserCache(user_id); - if (cache_.has_value()) { - cache_.value().isUpdated = false; - setUserCache(user_id, cache_.value()); - } else { - setUserCache(user_id, UserCache{}); - } - }, - Qt::QueuedConnection); - connect( - this, - &Cache::deleteLeftUsers, - this, - [this](const std::string &user_id) { deleteUserCache(user_id); }, - Qt::QueuedConnection); + connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); } void @@ -1017,6 +999,8 @@ Cache::saveState(const mtx::responses::Sync &res) using namespace mtx::events; auto user_id = this->localUserId_.toStdString(); + auto currentBatchToken = nextBatchToken(); + auto txn = lmdb::txn::begin(env_); setNextBatchToken(txn, res.next_batch); @@ -1034,6 +1018,8 @@ Cache::saveState(const mtx::responses::Sync &res) ev); } + auto userKeyCacheDb = getUserKeysDb(txn); + // Save joined rooms for (const auto &room : res.rooms.join) { auto statesdb = getStatesDb(txn, room.first); @@ -1107,7 +1093,8 @@ Cache::saveState(const mtx::responses::Sync &res) savePresence(txn, res.presence); - updateUserCache(res.device_lists); + markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken); + deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left); removeLeftRooms(txn, res.rooms.leave); @@ -3098,126 +3085,246 @@ Cache::statusMessage(const std::string &user_id) } void -to_json(json &j, const UserCache &info) +to_json(json &j, const UserKeyCache &info) { - j["keys"] = info.keys; - j["isUpdated"] = info.isUpdated; + j["device_keys"] = info.device_keys; + j["master_keys"] = info.master_keys; + j["user_signing_keys"] = info.user_signing_keys; + j["self_signing_keys"] = info.self_signing_keys; + j["updated_at"] = info.updated_at; + j["last_changed"] = info.last_changed; } void -from_json(const json &j, UserCache &info) +from_json(const json &j, UserKeyCache &info) { - info.keys = j.at("keys").get(); - info.isUpdated = j.at("isUpdated").get(); + info.device_keys = j.value("device_keys", std::map{}); + info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); + info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.updated_at = j.value("updated_at", ""); + info.last_changed = j.value("last_changed", ""); } -std::optional -Cache::getUserCache(const std::string &user_id) +std::optional +Cache::userKeys(const std::string &user_id) { - lmdb::val verifiedVal; + lmdb::val keys; - auto txn = lmdb::txn::begin(env_); - auto db = getUserCacheDb(txn); - auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); - - txn.commit(); + try { + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + auto db = getUserKeysDb(txn); + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), keys); - UserCache verified_state; - if (res) { - verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); - return verified_state; - } else { + if (res) { + return json::parse(std::string_view(keys.data(), keys.size())) + .get(); + } else { + return {}; + } + } catch (std::exception &) { return {}; } } -//! be careful when using make sure is_user_verified is not changed -int -Cache::setUserCache(const std::string &user_id, const UserCache &body) +void +Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { auto txn = lmdb::txn::begin(env_); - auto db = getUserCacheDb(txn); + auto db = getUserKeysDb(txn); - auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + std::map updates; - txn.commit(); + for (const auto &[user, keys] : keyQuery.device_keys) + updates[user].device_keys = keys; + for (const auto &[user, keys] : keyQuery.master_keys) + updates[user].master_keys = keys; + for (const auto &[user, keys] : keyQuery.user_signing_keys) + updates[user].user_signing_keys = keys; + for (const auto &[user, keys] : keyQuery.self_signing_keys) + updates[user].self_signing_keys = keys; - return res; + for (auto &[user, update] : updates) { + lmdb::val oldKeys; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys); + + if (res) { + auto last_changed = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())) + .get() + .last_changed; + // skip if we are tracking this and expect it to be up to date with the last + // sync token + if (!last_changed.empty() && last_changed != sync_token) + continue; + } + lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(update).dump())); + } + + txn.commit(); } void -Cache::updateUserCache(const mtx::responses::DeviceLists body) +Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector &user_ids) { - for (std::string user_id : body.changed) { - emit updateUserCacheFlag(user_id); - } - - for (std::string user_id : body.left) { - emit deleteLeftUsers(user_id); - } + for (const auto &user_id : user_ids) + lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr); } -int -Cache::deleteUserCache(const std::string &user_id) +void +Cache::markUserKeysOutOfDate(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids, + const std::string &sync_token) { - auto txn = lmdb::txn::begin(env_); - auto db = getUserCacheDb(txn); - auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr); + mtx::requests::QueryKeys query; + query.token = sync_token; - txn.commit(); + for (const auto &user : user_ids) { + lmdb::val oldKeys; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys); - return res; + if (!res) + continue; + + auto cacheEntry = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get(); + cacheEntry.last_changed = sync_token; + lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(cacheEntry).dump())); + + query.device_keys[user] = {}; + } + + if (!query.device_keys.empty()) + http::client()->query_keys(query, + [this, sync_token](const mtx::responses::QueryKeys &keys, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to query device keys: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + emit userKeysUpdate(sync_token, keys); + }); } void -to_json(json &j, const DeviceVerifiedCache &info) +to_json(json &j, const VerificationCache &info) { - j["is_user_verified"] = info.is_user_verified; - j["cross_verified"] = info.cross_verified; - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["verified_master_key"] = info.verified_master_key; + j["cross_verified"] = info.cross_verified; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void -from_json(const json &j, DeviceVerifiedCache &info) +from_json(const json &j, VerificationCache &info) { - info.is_user_verified = j.at("is_user_verified"); - info.cross_verified = j.at("cross_verified").get>(); - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.verified_master_key = j.at("verified_master_key"); + info.cross_verified = j.at("cross_verified").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } -std::optional -Cache::getVerifiedCache(const std::string &user_id) +std::optional +Cache::verificationStatus(const std::string &user_id) { lmdb::val verifiedVal; auto txn = lmdb::txn::begin(env_); - auto db = getDeviceVerifiedDb(txn); - auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + auto db = getVerificationDb(txn); - txn.commit(); - - DeviceVerifiedCache verified_state; - if (res) { - verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); - return verified_state; - } else { + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + if (res) { + verified_state = + json::parse(std::string_view(verifiedVal.data(), verifiedVal.size())); + return verified_state; + } else { + return {}; + } + } catch (std::exception &) { return {}; } } -int -Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +void +Cache::markDeviceVerified(const std::string &user_id, const std::string &key) { + lmdb::val val; + auto txn = lmdb::txn::begin(env_); - auto db = getDeviceVerifiedDb(txn); + auto db = getVerificationDb(txn); - auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); + if (res) { + verified_state = json::parse(std::string_view(val.data(), val.size())); + } - txn.commit(); + for (const auto &device : verified_state.device_verified) + if (device == key) + return; - return res; + verified_state.device_verified.push_back(key); + lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); + txn.commit(); + } catch (std::exception &) { + } +} + +void +Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) +{ + lmdb::val val; + + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); + + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); + if (res) { + verified_state = json::parse(std::string_view(val.data(), val.size())); + } + + verified_state.device_verified.erase( + std::remove(verified_state.device_verified.begin(), + verified_state.device_verified.end(), + key), + verified_state.device_verified.end()); + + lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); + txn.commit(); + } catch (std::exception &) { + } +} + +void +Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key) +{ + lmdb::val val; + + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); + + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); + if (res) { + verified_state = json::parse(std::string_view(val.data(), val.size())); + } + + verified_state.verified_master_key = key; + lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); + txn.commit(); + } catch (std::exception &) { + } } void @@ -3401,47 +3508,49 @@ statusMessage(const std::string &user_id) { return instance_->statusMessage(user_id); } -std::optional -getUserCache(const std::string &user_id) -{ - return instance_->getUserCache(user_id); -} +//! Load saved data for the display names & avatars. void -updateUserCache(const mtx::responses::DeviceLists body) +populateMembers() { - instance_->updateUserCache(body); + instance_->populateMembers(); } -int -setUserCache(const std::string &user_id, const UserCache &body) +// user cache stores user keys +std::optional +userKeys(const std::string &user_id) +{ + return instance_->userKeys(user_id); +} +void +updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { - return instance_->setUserCache(user_id, body); + instance_->updateUserKeys(sync_token, keyQuery); } -int -deleteUserCache(const std::string &user_id) +// device & user verification cache +std::optional +verificationStatus(const std::string &user_id) { - return instance_->deleteUserCache(user_id); + return instance_->verificationStatus(user_id); } -std::optional -getVerifiedCache(const std::string &user_id) +void +markDeviceVerified(const std::string &user_id, const std::string &key) { - return instance_->getVerifiedCache(user_id); + instance_->markDeviceVerified(user_id, key); } -int -setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +void +markDeviceUnverified(const std::string &user_id, const std::string &key) { - return instance_->setVerifiedCache(user_id, body); + instance_->markDeviceUnverified(user_id, key); } -//! Load saved data for the display names & avatars. void -populateMembers() +markMasterKeyVerified(const std::string &user_id, const std::string &key) { - instance_->populateMembers(); + instance_->markMasterKeyVerified(user_id, key); } std::vector diff --git a/src/Cache.h b/src/Cache.h index 82d909a..edad599 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -60,25 +60,21 @@ presenceState(const std::string &user_id); std::string statusMessage(const std::string &user_id); -//! user Cache -std::optional -getUserCache(const std::string &user_id); - +// user cache stores user keys +std::optional +userKeys(const std::string &user_id); void -updateUserCache(const mtx::responses::DeviceLists body); - -int -setUserCache(const std::string &user_id, const UserCache &body); - -int -deleteUserCache(const std::string &user_id); - -//! verified Cache -std::optional -getVerifiedCache(const std::string &user_id); +updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); -int -setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); +// device & user verification cache +std::optional +verificationStatus(const std::string &user_id); +void +markDeviceVerified(const std::string &user_id, const std::string &key); +void +markDeviceUnverified(const std::string &user_id, const std::string &key); +void +markMasterKeyVerified(const std::string &user_id, const std::string &key); //! Load saved data for the display names & avatars. void diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 1dde21c..10636ac 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -67,52 +67,38 @@ struct OlmSessionStorage }; // this will store the keys of the user with whom a encrypted room is shared with -struct UserCache +struct UserKeyCache { - //! map of public key key_ids and their public_key - mtx::responses::QueryKeys keys; - //! if the current cache is updated or not - bool isUpdated = false; - - UserCache(mtx::responses::QueryKeys res, bool isUpdated_ = false) - : keys(res) - , isUpdated(isUpdated_) - {} - UserCache() {} + //! Device id to device keys + std::map device_keys; + //! corss signing keys + mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys; + //! Sync token when nheko last fetched the keys + std::string updated_at; + //! Sync token when the keys last changed. updated != last_changed means they are outdated. + std::string last_changed; }; void -to_json(nlohmann::json &j, const UserCache &info); +to_json(nlohmann::json &j, const UserKeyCache &info); void -from_json(const nlohmann::json &j, UserCache &info); +from_json(const nlohmann::json &j, UserKeyCache &info); // the reason these are stored in a seperate cache rather than storing it in the user cache is -// UserCache stores only keys of users with which encrypted room is shared -struct DeviceVerifiedCache +// UserKeyCache stores only keys of users with which encrypted room is shared +struct VerificationCache { //! list of verified device_ids with device-verification std::vector device_verified; - //! list of verified device_ids with cross-signing + //! list of verified device_ids with cross-signing, calculated from master key std::vector cross_verified; //! list of devices the user blocks std::vector device_blocked; - //! this stores if the user is verified (with cross-signing) - bool is_user_verified = false; - - DeviceVerifiedCache(std::vector device_verified_, - std::vector cross_verified_, - std::vector device_blocked_, - bool is_user_verified_ = false) - : device_verified(device_verified_) - , cross_verified(cross_verified_) - , device_blocked(device_blocked_) - , is_user_verified(is_user_verified_) - {} - - DeviceVerifiedCache() {} + //! The verified master key. + std::string verified_master_key; }; void -to_json(nlohmann::json &j, const DeviceVerifiedCache &info); +to_json(nlohmann::json &j, const VerificationCache &info); void -from_json(const nlohmann::json &j, DeviceVerifiedCache &info); +from_json(const nlohmann::json &j, VerificationCache &info); diff --git a/src/Cache_p.h b/src/Cache_p.h index ce6414a..034c6d7 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -55,14 +55,22 @@ public: std::string statusMessage(const std::string &user_id); // user cache stores user keys - std::optional getUserCache(const std::string &user_id); - void updateUserCache(const mtx::responses::DeviceLists body); - int setUserCache(const std::string &user_id, const UserCache &body); - int deleteUserCache(const std::string &user_id); - - // device verified cache - std::optional getVerifiedCache(const std::string &user_id); - int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); + std::optional userKeys(const std::string &user_id); + void updateUserKeys(const std::string &sync_token, + const mtx::responses::QueryKeys &keyQuery); + void markUserKeysOutOfDate(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids, + const std::string &sync_token); + void deleteUserKeys(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids); + + // device & user verification cache + std::optional verificationStatus(const std::string &user_id); + void markDeviceVerified(const std::string &user_id, const std::string &key); + void markDeviceUnverified(const std::string &user_id, const std::string &key); + void markMasterKeyVerified(const std::string &user_id, const std::string &key); static void removeDisplayName(const QString &room_id, const QString &user_id); static void removeAvatarUrl(const QString &room_id, const QString &user_id); @@ -272,8 +280,8 @@ signals: void newReadReceipts(const QString &room_id, const std::vector &event_ids); void roomReadStatus(const std::map &status); void removeNotification(const QString &room_id, const QString &event_id); - void updateUserCacheFlag(const std::string &user_id); - void deleteLeftUsers(const std::string &user_id); + void userKeysUpdate(const std::string &sync_token, + const mtx::responses::QueryKeys &keyQuery); private: //! Save an invited room. @@ -539,12 +547,12 @@ private: return lmdb::dbi::open(txn, "presence", MDB_CREATE); } - lmdb::dbi getUserCacheDb(lmdb::txn &txn) + lmdb::dbi getUserKeysDb(lmdb::txn &txn) { - return lmdb::dbi::open(txn, "user_cache", MDB_CREATE); + return lmdb::dbi::open(txn, "user_key", MDB_CREATE); } - lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn) + lmdb::dbi getVerificationDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "verified", MDB_CREATE); } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index c6978a5..6abe407 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1466,35 +1466,43 @@ ChatPage::initiateLogout() } void -ChatPage::query_keys( - const mtx::requests::QueryKeys &req, - std::function cb) +ChatPage::query_keys(const std::string &user_id, + std::function cb) { - std::string user_id = req.device_keys.begin()->first; - auto cache_ = cache::getUserCache(user_id); + auto cache_ = cache::userKeys(user_id); if (cache_.has_value()) { - if (cache_.value().isUpdated) { - cb(cache_.value().keys, {}); - } else { - http::client()->query_keys( - req, - [cb, user_id](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - err->matrix_error.errcode, - static_cast(err->status_code)); - return; - } - cache::setUserCache(std::move(user_id), - std::move(UserCache{res, true})); - cb(res, err); - }); + if (!cache_->updated_at.empty() && cache_->updated_at == cache_->last_changed) { + cb(cache_.value(), {}); + return; } - } else { - http::client()->query_keys(req, cb); } + + mtx::requests::QueryKeys req; + req.device_keys[user_id] = {}; + + std::string last_changed; + if (cache_) + last_changed = cache_->last_changed; + req.token = last_changed; + + http::client()->query_keys(req, + [cb, user_id, last_changed](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + cb({}, err); + return; + } + + cache::updateUserKeys(last_changed, res); + + auto keys = cache::userKeys(user_id); + cb(keys.value_or(UserKeyCache{}), err); + }); } template diff --git a/src/ChatPage.h b/src/ChatPage.h index 9d8abb2..f363c4f 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -35,6 +35,7 @@ #include #include +#include "CacheCryptoStructs.h" #include "CacheStructs.h" #include "CallManager.h" #include "CommunitiesList.h" @@ -89,9 +90,8 @@ public: //! Show the room/group list (if it was visible). void showSideBars(); void initiateLogout(); - void query_keys( - const mtx::requests::QueryKeys &req, - std::function cb); + void query_keys(const std::string &req, + std::function cb); void focusMessageInput(); QString status() const; diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 96fed55..aa8b5b4 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -328,22 +328,14 @@ DeviceVerificationFlow::setTransactionId(QString transaction_id_) void DeviceVerificationFlow::setUserId(QString userID) { - this->userId = userID; - this->toClient = mtx::identifiers::parse(userID.toStdString()); - auto user_cache = cache::getUserCache(userID.toStdString()); - - if (user_cache.has_value()) { - this->callback_fn(user_cache->keys, {}, userID.toStdString()); - } else { - mtx::requests::QueryKeys req; - req.device_keys[userID.toStdString()] = {}; - http::client()->query_keys( - req, - [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - this->callback_fn(res, err, user_id); - }); - } + this->userId = userID; + this->toClient = mtx::identifiers::parse(userID.toStdString()); + + auto user_id = userID.toStdString(); + ChatPage::instance()->query_keys( + user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { + this->callback_fn(res, err, user_id); + }); } void @@ -622,30 +614,52 @@ DeviceVerificationFlow::sendVerificationKey() (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationKey); } } -//! sends the mac of the keys -void -DeviceVerificationFlow::sendVerificationMac() + +mtx::events::msg::KeyVerificationMac +key_verification_mac(mtx::crypto::SAS *sas, + mtx::identifiers::User sender, + const std::string &senderDevice, + mtx::identifiers::User receiver, + const std::string &receiverDevice, + const std::string &transactionId, + std::map keys) { mtx::events::msg::KeyVerificationMac req; - std::string info = "MATRIX_KEY_VERIFICATION_MAC" + http::client()->user_id().to_string() + - http::client()->device_id() + this->toClient.to_string() + - this->deviceId.toStdString() + this->transaction_id; - - //! this vector stores the type of the key and the key - std::vector> key_list; - key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519)); - std::sort(key_list.begin(), key_list.end()); - for (auto x : key_list) { - req.mac.insert( - std::make_pair(x.first + ":" + http::client()->device_id(), - this->sas->calculate_mac( - x.second, info + x.first + ":" + http::client()->device_id()))); - req.keys += x.first + ":" + http::client()->device_id() + ","; + std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice + + receiver.to_string() + receiverDevice + transactionId; + + std::string key_list; + bool first = true; + for (const auto &[key_id, key] : keys) { + req.mac[key_id] = sas->calculate_mac(key, info + key_id); + + if (!first) + key_list += ","; + key_list += key_id; + first = false; } - req.keys = - this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS"); + req.keys = sas->calculate_mac(key_list, info + "KEY_IDS"); + + return req; +} + +//! sends the mac of the keys +void +DeviceVerificationFlow::sendVerificationMac() +{ + std::map key_list; + key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519; + + mtx::events::msg::KeyVerificationMac req = + key_verification_mac(sas.get(), + http::client()->user_id(), + http::client()->device_id(), + this->toClient, + this->deviceId.toStdString(), + this->transaction_id, + key_list); if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; @@ -673,27 +687,16 @@ DeviceVerificationFlow::sendVerificationMac() void DeviceVerificationFlow::acceptDevice() { - auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); - if (verified_cache.has_value()) { - verified_cache->device_verified.push_back(this->deviceId.toStdString()); - verified_cache->device_blocked.erase( - std::remove(verified_cache->device_blocked.begin(), - verified_cache->device_blocked.end(), - this->deviceId.toStdString()), - verified_cache->device_blocked.end()); - } else { - cache::setVerifiedCache( - this->userId.toStdString(), - DeviceVerifiedCache{{this->deviceId.toStdString()}, {}, {}}); - } + cache::markDeviceVerified(this->userId.toStdString(), this->deviceId.toStdString()); emit deviceVerified(); emit refreshProfile(); this->deleteLater(); } + //! callback function to keep track of devices void -DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res, +DeviceVerificationFlow::callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id) { @@ -704,35 +707,22 @@ DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res, return; } - if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) { + if (res.device_keys.empty() || + (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { nhlog::net()->warn("no devices retrieved {}", user_id); return; } - for (auto x : res.device_keys) { - for (auto y : x.second) { - auto z = y.second; - if (z.user_id == user_id && z.device_id == this->deviceId.toStdString()) { - for (auto a : z.keys) { - // TODO: Verify Signatures - this->device_keys[a.first] = a.second; - } - } - } + for (const auto &[algorithm, key] : res.device_keys.at(deviceId.toStdString()).keys) { + // TODO: Verify Signatures + this->device_keys[algorithm] = key; } } void DeviceVerificationFlow::unverify() { - auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); - if (verified_cache.has_value()) { - auto it = std::remove(verified_cache->device_verified.begin(), - verified_cache->device_verified.end(), - this->deviceId.toStdString()); - verified_cache->device_verified.erase(it); - cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value()); - } + cache::markDeviceUnverified(this->userId.toStdString(), this->deviceId.toStdString()); emit refreshProfile(); } diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index b85cbec..31d2fac 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -1,10 +1,12 @@ #pragma once -#include "Olm.h" +#include + +#include +#include "CacheCryptoStructs.h" #include "MatrixClient.h" -#include "mtx/responses/crypto.hpp" -#include +#include "Olm.h" class QTimer; @@ -71,9 +73,7 @@ public: void setSender(bool sender_); void setEventId(std::string event_id); - void callback_fn(const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err, - std::string user_id); + void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); nlohmann::json canonical_json; diff --git a/src/timeline/.TimelineModel.cpp.swn b/src/timeline/.TimelineModel.cpp.swn deleted file mode 100644 index 9e96570264bc9cbab5e9b87ccf0e1880e0db3eea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237568 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

`E(A&g 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 F{|AftumJ!7 diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 08c3009..2ea3f7b 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -89,12 +89,10 @@ UserProfile::fetchDeviceList(const QString &userID) { auto localUser = utils::localUser(); - mtx::requests::QueryKeys req; - req.device_keys[userID.toStdString()] = {}; ChatPage::instance()->query_keys( - req, - [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { + userID.toStdString(), + [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys, + mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", err->matrix_error.errcode, @@ -102,20 +100,11 @@ UserProfile::fetchDeviceList(const QString &userID) return; } - if (res.device_keys.empty() || - (res.device_keys.find(user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; - } - // Finding if the User is Verified or not based on the Signatures - mtx::requests::QueryKeys req; - req.device_keys[utils::localUser().toStdString()] = {}; - ChatPage::instance()->query_keys( - req, - [user_id, other_res = res, this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { + utils::localUser().toStdString(), + [other_user_id, other_user_keys, this](const UserKeyCache &res, + mtx::http::RequestErr err) { using namespace mtx; std::string local_user_id = utils::localUser().toStdString(); @@ -126,34 +115,28 @@ UserProfile::fetchDeviceList(const QString &userID) return; } - if (res.device_keys.empty() || - (res.device_keys.find(local_user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); + if (res.device_keys.empty()) { + nhlog::net()->warn("no devices retrieved {}", local_user_id); return; } std::vector deviceInfo; - auto devices = other_res.device_keys.at(user_id); - auto device_verified = cache::getVerifiedCache(user_id); + auto devices = other_user_keys.device_keys; + auto device_verified = cache::verificationStatus(other_user_id); if (device_verified.has_value()) { - isUserVerified = device_verified.value().is_user_verified; + // TODO: properly check cross-signing signatures here + isUserVerified = !device_verified->verified_master_key.empty(); } std::optional lmk, lsk, luk, mk, sk, uk; - if (!res.master_keys.empty()) - lmk = res.master_keys.at(local_user_id); - if (!res.user_signing_keys.empty()) - luk = res.user_signing_keys.at(local_user_id); - if (!res.self_signing_keys.empty()) - lsk = res.self_signing_keys.at(local_user_id); - if (!other_res.master_keys.empty()) - mk = other_res.master_keys.at(user_id); - if (!other_res.user_signing_keys.empty()) - uk = other_res.user_signing_keys.at(user_id); - if (!other_res.self_signing_keys.empty()) - sk = other_res.self_signing_keys.at(user_id); + lmk = res.master_keys; + luk = res.user_signing_keys; + lsk = res.self_signing_keys; + mk = other_user_keys.master_keys; + uk = other_user_keys.user_signing_keys; + sk = other_user_keys.self_signing_keys; // First checking if the user is verified if (luk.has_value() && mk.has_value()) { @@ -202,7 +185,7 @@ UserProfile::fetchDeviceList(const QString &userID) device_verified->device_blocked.end()) verified = verification::Status::BLOCKED; } else if (isUserVerified) { - device_verified = DeviceVerifiedCache{}; + device_verified = VerificationCache{}; } // won't check for already verified devices @@ -211,7 +194,7 @@ UserProfile::fetchDeviceList(const QString &userID) if ((sk.has_value()) && (!device.signatures.empty())) { for (auto sign_key : sk.value().keys) { auto signs = - device.signatures.at(user_id); + device.signatures.at(other_user_id); try { if (olm::client() ->ed25519_verify_sig( @@ -232,12 +215,13 @@ UserProfile::fetchDeviceList(const QString &userID) } } - if (device_verified.has_value()) { - device_verified.value().is_user_verified = - isUserVerified; - cache::setVerifiedCache(user_id, - device_verified.value()); - } + // TODO(Nico): properly show cross-signing + // if (device_verified.has_value()) { + // device_verified.value().is_user_verified = + // isUserVerified; + // cache::setVerifiedCache(user_id, + // device_verified.value()); + //} deviceInfo.push_back( {QString::fromStdString(d.first), From 54e3c2c96b75052da8249c3ce92065b9ad09f139 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 12:18:54 +0200 Subject: [PATCH 52/70] Fix verification start to start with request --- resources/qml/device-verification/DeviceVerification.qml | 1 - .../qml/device-verification/NewVerificationRequest.qml | 2 +- src/DeviceVerificationFlow.cpp | 7 +++---- src/DeviceVerificationFlow.h | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index e409b0f..ca98098 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -6,7 +6,6 @@ import im.nheko 1.0 ApplicationWindow { property var flow - property bool isRequest property var tran_id title: stack.currentItem.title diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index d8fc65a..06b06cf 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -64,7 +64,7 @@ Pane { } onClicked: { stack.replace(awaitingVerificationRequestAccept); - isRequest?flow.sendVerificationRequest():flow.startVerificationRequest(); } + flow.sender ?flow.sendVerificationRequest():flow.startVerificationRequest(); } } } } diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index aa8b5b4..7b367de 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -498,14 +498,13 @@ DeviceVerificationFlow::sendVerificationRequest() mtx::events::msg::KeyVerificationRequest req; req.from_device = http::client()->device_id(); - req.methods.resize(1); - req.methods[0] = mtx::events::msg::VerificationMethods::SASv1; + req.methods = {mtx::events::msg::VerificationMethods::SASv1}; if (this->type == DeviceVerificationFlow::Type::ToDevice) { - QDateTime CurrentTime = QDateTime::currentDateTimeUtc(); + QDateTime currentTime = QDateTime::currentDateTimeUtc(); req.transaction_id = this->transaction_id; - req.timestamp = (uint64_t)CurrentTime.toTime_t(); + req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch(); mtx::requests::ToDeviceMessages body; diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 31d2fac..de7a456 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -111,7 +111,7 @@ private: // general QString userId; QString deviceId; - Method method; + Method method = Method::Emoji; Type type; bool sender; QTimer *timeout = nullptr; From c9de044e323888e68f4bbb275e89616ca991498f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 13:46:32 +0200 Subject: [PATCH 53/70] Handle forwarded room keys --- src/Olm.cpp | 107 +++++++++++++++++++++++++++++++--------------------- src/Olm.h | 10 +++-- 2 files changed, 69 insertions(+), 48 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 4f0c589..8219ce4 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -138,6 +138,17 @@ handle_olm_message(const OlmMessage &msg) auto payload = try_olm_decryption(msg.sender_key, cipher.second); + if (payload.is_null()) { + // Check for PRE_KEY message + if (cipher.second.type == 0) { + payload = handle_pre_key_olm_message( + msg.sender, msg.sender_key, cipher.second); + } else { + nhlog::crypto()->error("Undecryptable olm message!"); + continue; + } + } + if (!payload.is_null()) { std::string msg_type = payload["type"]; @@ -180,26 +191,23 @@ handle_olm_message(const OlmMessage &msg) ChatPage::instance()->recievedDeviceVerificationDone( payload["content"]); return; + } else if (msg_type == to_string(mtx::events::EventType::RoomKey)) { + mtx::events::DeviceEvent roomKey = + payload; + create_inbound_megolm_session(roomKey, msg.sender_key); + return; + } else if (msg_type == + to_string(mtx::events::EventType::ForwardedRoomKey)) { + mtx::events::DeviceEvent + roomKey = payload; + import_inbound_megolm_session(roomKey); + return; } } - - if (!payload.is_null()) { - nhlog::crypto()->debug("decrypted olm payload: {}", payload.dump(2)); - create_inbound_megolm_session(msg.sender, msg.sender_key, payload); - return; - } - - // Not a PRE_KEY message - if (cipher.second.type != 0) { - // TODO: log that it should have matched something - return; - } - - handle_pre_key_olm_message(msg.sender, msg.sender_key, cipher.second); } } -void +nlohmann::json handle_pre_key_olm_message(const std::string &sender, const std::string &sender_key, const mtx::events::msg::OlmCipherContent &content) @@ -217,14 +225,14 @@ handle_pre_key_olm_message(const std::string &sender, } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical( "failed to create inbound session with {}: {}", sender, e.what()); - return; + return {}; } if (!mtx::crypto::matches_inbound_session_from( inbound_session.get(), sender_key, content.body)) { nhlog::crypto()->warn("inbound olm session doesn't match sender's key ({})", sender); - return; + return {}; } mtx::crypto::BinaryBuf output; @@ -234,7 +242,7 @@ handle_pre_key_olm_message(const std::string &sender, } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical( "failed to decrypt olm message {}: {}", content.body, e.what()); - return; + return {}; } auto plaintext = json::parse(std::string((char *)output.data(), output.size())); @@ -247,7 +255,7 @@ handle_pre_key_olm_message(const std::string &sender, "failed to save inbound olm session from {}: {}", sender, e.what()); } - create_inbound_megolm_session(sender, sender_key, plaintext); + return plaintext; } mtx::events::msg::Encrypted @@ -323,10 +331,12 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip } try { - return json::parse(std::string((char *)text.data(), text.size())); + return json::parse(std::string_view((char *)text.data(), text.size())); } catch (const json::exception &e) { - nhlog::crypto()->critical("failed to parse the decrypted session msg: {}", - e.what()); + nhlog::crypto()->critical( + "failed to parse the decrypted session msg: {} {}", + e.what(), + std::string_view((char *)text.data(), text.size())); } } @@ -334,39 +344,54 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip } void -create_inbound_megolm_session(const std::string &sender, - const std::string &sender_key, - const nlohmann::json &payload) +create_inbound_megolm_session(const mtx::events::DeviceEvent &roomKey, + const std::string &sender_key) { - std::string room_id, session_id, session_key; + MegolmSessionIndex index; + index.room_id = roomKey.content.room_id; + index.session_id = roomKey.content.session_id; + index.sender_key = sender_key; try { - room_id = payload.at("content").at("room_id"); - session_id = payload.at("content").at("session_id"); - session_key = payload.at("content").at("session_key"); - } catch (const nlohmann::json::exception &e) { - nhlog::crypto()->critical( - "failed to parse plaintext olm message: {} {}", e.what(), payload.dump(2)); + auto megolm_session = + olm::client()->init_inbound_group_session(roomKey.content.session_key); + cache::saveInboundMegolmSession(index, std::move(megolm_session)); + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to create inbound megolm session: {}", e.what()); return; } + nhlog::crypto()->info( + "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); +} + +void +import_inbound_megolm_session( + const mtx::events::DeviceEvent &roomKey) +{ MegolmSessionIndex index; - index.room_id = room_id; - index.session_id = session_id; - index.sender_key = sender_key; + index.room_id = roomKey.content.room_id; + index.session_id = roomKey.content.session_id; + index.sender_key = roomKey.content.sender_key; try { - auto megolm_session = olm::client()->init_inbound_group_session(session_key); + auto megolm_session = + olm::client()->import_inbound_group_session(roomKey.content.session_key); cache::saveInboundMegolmSession(index, std::move(megolm_session)); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); return; } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to create inbound megolm session: {}", e.what()); + nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); return; } - nhlog::crypto()->info("established inbound megolm session ({}, {})", room_id, sender); + // TODO(Nico): Reload messages encrypted with this key. + nhlog::crypto()->info( + "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); } void @@ -493,12 +518,6 @@ handle_key_request_message(const mtx::events::DeviceEventdebug("ignoring all key requests for room {}", - req.content.room_id); - - nhlog::crypto()->debug("ignoring all key requests for room {}", - req.content.room_id); - nhlog::crypto()->debug("ignoring all key requests for room {}", req.content.room_id); return; diff --git a/src/Olm.h b/src/Olm.h index 87f4e3e..7b97039 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -71,11 +71,13 @@ handle_olm_message(const OlmMessage &msg); //! Establish a new inbound megolm session with the decrypted payload from olm. void -create_inbound_megolm_session(const std::string &sender, - const std::string &sender_key, - const nlohmann::json &payload); - +create_inbound_megolm_session(const mtx::events::DeviceEvent &roomKey, + const std::string &sender_key); void +import_inbound_megolm_session( + const mtx::events::DeviceEvent &roomKey); + +nlohmann::json handle_pre_key_olm_message(const std::string &sender, const std::string &sender_key, const mtx::events::msg::OlmCipherContent &content); From 600df6d2ec84df5238781f1274b6f5ff31a34646 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 16:39:20 +0200 Subject: [PATCH 54/70] Fix fetching inexistent batch tokens --- src/Cache.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 8b47c35..b7a2534 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -687,11 +687,14 @@ Cache::nextBatchToken() const auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); lmdb::val token; - lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token); + auto result = lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token); txn.commit(); - return std::string(token.data(), token.size()); + if (result) + return std::string(token.data(), token.size()); + else + return ""; } void From d4dccc8e366c01e61186b3bbec7b3491322ad10a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 16:58:13 +0200 Subject: [PATCH 55/70] Fix room name --- resources/qml/TimelineView.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e656221..3f72a7d 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -213,6 +213,7 @@ Page { Layout.fillWidth: true Layout.column: 2 Layout.row: 0 + color: colors.text font.pointSize: fontMetrics.font.pointSize * 1.1 From 51964c4fd7f41e9b1a394d0787897d8cd9ba771e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 17:33:04 +0200 Subject: [PATCH 56/70] Clean up verification pages a bit --- .../AcceptNewVerificationRequest.qml | 22 +++---------------- .../AwaitingVerificationConfirmation.qml | 10 +-------- .../AwaitingVerificationRequest.qml | 10 +-------- .../device-verification/DigitVerification.qml | 22 +++---------------- .../device-verification/EmojiVerification.qml | 20 ++--------------- .../NewVerificationRequest.qml | 22 +++---------------- .../device-verification/PartnerAborted.qml | 12 ++-------- .../qml/device-verification/TimedOut.qml | 11 +--------- .../VerificationSuccess.qml | 10 +-------- 9 files changed, 17 insertions(+), 122 deletions(-) diff --git a/resources/qml/device-verification/AcceptNewVerificationRequest.qml b/resources/qml/device-verification/AcceptNewVerificationRequest.qml index 872fabe..5bdbc4a 100644 --- a/resources/qml/device-verification/AcceptNewVerificationRequest.qml +++ b/resources/qml/device-verification/AcceptNewVerificationRequest.qml @@ -25,15 +25,7 @@ Pane { Button { Layout.alignment: Qt.AlignLeft text: qsTr("Deny") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { flow.cancelVerification(DeviceVerificationFlow.User); deviceVerificationList.remove(tran_id); @@ -46,18 +38,10 @@ Pane { Button { Layout.alignment: Qt.AlignRight text: qsTr("Accept") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { stack.replace(awaitingVerificationRequestAccept); - isRequest?flow.sendVerificationReady():flow.acceptVerificationRequest(); + flow.sender ?flow.sendVerificationReady():flow.acceptVerificationRequest(); } } } diff --git a/resources/qml/device-verification/AwaitingVerificationConfirmation.qml b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml index e078634..aaebba6 100644 --- a/resources/qml/device-verification/AwaitingVerificationConfirmation.qml +++ b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml @@ -25,15 +25,7 @@ Pane { Button { Layout.alignment: Qt.AlignLeft text: qsTr("Cancel") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { flow.cancelVerification(DeviceVerificationFlow.User); deviceVerificationList.remove(tran_id); diff --git a/resources/qml/device-verification/AwaitingVerificationRequest.qml b/resources/qml/device-verification/AwaitingVerificationRequest.qml index 22a504c..b4b9178 100644 --- a/resources/qml/device-verification/AwaitingVerificationRequest.qml +++ b/resources/qml/device-verification/AwaitingVerificationRequest.qml @@ -25,15 +25,7 @@ Pane { Button { Layout.alignment: Qt.AlignLeft text: qsTr("Cancel") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { flow.cancelVerification(DeviceVerificationFlow.User); deviceVerificationList.remove(tran_id); diff --git a/resources/qml/device-verification/DigitVerification.qml b/resources/qml/device-verification/DigitVerification.qml index 241ccbd..f3b1f5c 100644 --- a/resources/qml/device-verification/DigitVerification.qml +++ b/resources/qml/device-verification/DigitVerification.qml @@ -43,15 +43,7 @@ Pane { Button { Layout.alignment: Qt.AlignLeft text: qsTr("They do not match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); deviceVerificationList.remove(tran_id); @@ -64,17 +56,9 @@ Pane { Button { Layout.alignment: Qt.AlignRight text: qsTr("They match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } } } } -} \ No newline at end of file +} diff --git a/resources/qml/device-verification/EmojiVerification.qml b/resources/qml/device-verification/EmojiVerification.qml index fae08f2..19faf1b 100644 --- a/resources/qml/device-verification/EmojiVerification.qml +++ b/resources/qml/device-verification/EmojiVerification.qml @@ -123,15 +123,7 @@ Pane { Button { Layout.alignment: Qt.AlignLeft text: qsTr("They do not match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); deviceVerificationList.remove(tran_id); @@ -144,15 +136,7 @@ Pane { Button { Layout.alignment: Qt.AlignRight text: qsTr("They match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } } } diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index 06b06cf..ef730b1 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -32,15 +32,7 @@ Pane { Button { Layout.alignment: Qt.AlignLeft text: qsTr("Cancel") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { deviceVerificationList.remove(tran_id); flow.deleteFlow(); @@ -53,19 +45,11 @@ Pane { Button { Layout.alignment: Qt.AlignRight text: qsTr("Start verification") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { stack.replace(awaitingVerificationRequestAccept); flow.sender ?flow.sendVerificationRequest():flow.startVerificationRequest(); } + } } } } -} diff --git a/resources/qml/device-verification/PartnerAborted.qml b/resources/qml/device-verification/PartnerAborted.qml index 62787b1..6174477 100644 --- a/resources/qml/device-verification/PartnerAborted.qml +++ b/resources/qml/device-verification/PartnerAborted.qml @@ -23,15 +23,7 @@ Pane { Button { Layout.alignment: Qt.AlignRight text: qsTr("Close") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { deviceVerificationList.remove(tran_id); dialog.destroy(); @@ -39,4 +31,4 @@ Pane { } } } -} \ No newline at end of file +} diff --git a/resources/qml/device-verification/TimedOut.qml b/resources/qml/device-verification/TimedOut.qml index 4052869..7dd0ab6 100644 --- a/resources/qml/device-verification/TimedOut.qml +++ b/resources/qml/device-verification/TimedOut.qml @@ -21,18 +21,9 @@ Pane { Layout.fillWidth: true } Button { - id: timedOutCancel Layout.alignment: Qt.AlignRight - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } text: qsTr("Close") + onClicked: { deviceVerificationList.remove(tran_id); flow.deleteFlow(); diff --git a/resources/qml/device-verification/VerificationSuccess.qml b/resources/qml/device-verification/VerificationSuccess.qml index c87488d..bc1e64f 100644 --- a/resources/qml/device-verification/VerificationSuccess.qml +++ b/resources/qml/device-verification/VerificationSuccess.qml @@ -23,15 +23,7 @@ Pane { Button { Layout.alignment: Qt.AlignRight text: qsTr("Close") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + onClicked: { deviceVerificationList.remove(tran_id); if(flow) flow.deleteFlow(); From abff61bb6cc69f93e9a571375489b22e72b072ab Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 3 Oct 2020 17:23:59 +0200 Subject: [PATCH 57/70] Fix nullopt when iterating reactions --- src/timeline/EventStore.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 298e0d1..29b3c23 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -390,7 +390,8 @@ EventStore::reactions(const std::string &event_id) continue; if (auto reaction = std::get_if>( - related_event)) { + related_event); + reaction && reaction->content.relates_to.key) { auto &agg = aggregation[reaction->content.relates_to.key.value()]; if (agg.count == 0) { From 2a79cd2b6b3548ac86a646b16f5a8d658fdb7282 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 3 Oct 2020 18:38:28 +0200 Subject: [PATCH 58/70] Don't fail on missing key for a device and /rotate-megolm-session command --- src/Cache.cpp | 24 ++++++++++++++++++++++++ src/Cache.h | 2 ++ src/Cache_p.h | 1 + src/ChatPage.cpp | 4 ++++ src/TextInputWidget.cpp | 2 ++ src/TextInputWidget.h | 1 + src/timeline/TimelineModel.cpp | 6 ++++++ 7 files changed, 40 insertions(+) diff --git a/src/Cache.cpp b/src/Cache.cpp index b7a2534..63f6e42 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -372,6 +372,25 @@ Cache::updateOutboundMegolmSession(const std::string &room_id, int message_index txn.commit(); } +void +Cache::dropOutboundMegolmSession(const std::string &room_id) +{ + using namespace mtx::crypto; + + if (!outboundMegolmSessionExists(room_id)) + return; + + { + std::unique_lock lock(session_storage.group_outbound_mtx); + session_storage.group_outbound_session_data.erase(room_id); + session_storage.group_outbound_sessions.erase(room_id); + + auto txn = lmdb::txn::begin(env_); + lmdb::dbi_del(txn, outboundMegolmSessionDb_, lmdb::val(room_id), nullptr); + txn.commit(); + } +} + void Cache::saveOutboundMegolmSession(const std::string &room_id, const OutboundGroupSessionData &data, @@ -3889,6 +3908,11 @@ updateOutboundMegolmSession(const std::string &room_id, int message_index) { instance_->updateOutboundMegolmSession(room_id, message_index); } +void +dropOutboundMegolmSession(const std::string &room_id) +{ + instance_->dropOutboundMegolmSession(room_id); +} void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) diff --git a/src/Cache.h b/src/Cache.h index edad599..fca8014 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -271,6 +271,8 @@ bool outboundMegolmSessionExists(const std::string &room_id) noexcept; void updateOutboundMegolmSession(const std::string &room_id, int message_index); +void +dropOutboundMegolmSession(const std::string &room_id); void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys); diff --git a/src/Cache_p.h b/src/Cache_p.h index 034c6d7..b37eae5 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -251,6 +251,7 @@ public: OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); bool outboundMegolmSessionExists(const std::string &room_id) noexcept; void updateOutboundMegolmSession(const std::string &room_id, int message_index); + void dropOutboundMegolmSession(const std::string &room_id); void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys); mtx::crypto::ExportedSessionKeys exportSessionKeys(); diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 6abe407..87b4c27 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -168,6 +168,10 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) view_manager_, &TimelineViewManager::clearCurrentRoomTimeline); + connect(text_input_, &TextInputWidget::rotateMegolmSession, this, [this]() { + cache::dropOutboundMegolmSession(current_room_.toStdString()); + }); + connect( new QShortcut(QKeySequence("Ctrl+Down"), this), &QShortcut::activated, this, [this]() { if (isVisible()) diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 4a25c4c..d1be7fb 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -709,6 +709,8 @@ TextInputWidget::command(QString command, QString args) emit sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\"); } else if (command == "clear-timeline") { emit clearRoomTimeline(); + } else if (command == "rotate-megolm-session") { + emit rotateMegolmSession(); } } diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index 3aa05c3..092e0ff 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -186,6 +186,7 @@ signals: void sendBanRoomRequest(const QString &userid, const QString &reason); void sendUnbanRoomRequest(const QString &userid, const QString &reason); void changeRoomNick(const QString &displayname); + void rotateMegolmSession(); void startedTyping(); void stoppedTyping(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index af26a54..5e8952f 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1120,6 +1120,12 @@ TimelineModel::handleClaimedKeys( nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); + if (rd.second.empty() || !rd.second.begin()->contains("key")) { + nhlog::net()->warn("Skipping device {} as it has no key.", + device_id); + continue; + } + // TODO: Verify signatures auto otk = rd.second.begin()->at("key"); From bca29a4227a871caac21236c29430b69264018ce Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 5 Oct 2020 22:12:10 +0200 Subject: [PATCH 59/70] Make steps in verification flow explicit --- resources/qml/TimelineView.qml | 14 +- resources/qml/UserProfile.qml | 31 +- .../AcceptNewVerificationRequest.qml | 49 -- .../AwaitingVerificationConfirmation.qml | 5 +- .../DeviceVerification.qml | 80 ++- .../device-verification/DigitVerification.qml | 12 +- .../device-verification/EmojiVerification.qml | 12 +- .../{TimedOut.qml => Failed.qml} | 11 +- .../NewVerificationRequest.qml | 37 +- .../device-verification/PartnerAborted.qml | 34 - .../{VerificationSuccess.qml => Success.qml} | 6 +- ...ingVerificationRequest.qml => Waiting.qml} | 13 +- resources/res.qrc | 9 +- src/ChatPage.h | 18 +- src/DeviceVerificationFlow.cpp | 613 +++++++++--------- src/DeviceVerificationFlow.h | 215 ++++-- src/Olm.cpp | 32 +- src/timeline/EventStore.cpp | 16 +- src/timeline/TimelineModel.cpp | 4 +- src/timeline/TimelineViewManager.cpp | 194 +++--- src/timeline/TimelineViewManager.h | 25 +- src/ui/UserProfile.cpp | 55 +- src/ui/UserProfile.h | 10 +- 23 files changed, 699 insertions(+), 796 deletions(-) delete mode 100644 resources/qml/device-verification/AcceptNewVerificationRequest.qml rename resources/qml/device-verification/{TimedOut.qml => Failed.qml} (50%) delete mode 100644 resources/qml/device-verification/PartnerAborted.qml rename resources/qml/device-verification/{VerificationSuccess.qml => Success.qml} (82%) rename resources/qml/device-verification/{AwaitingVerificationRequest.qml => Waiting.qml} (59%) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 3f72a7d..1dbe7c1 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -106,19 +106,7 @@ Page { Connections { target: TimelineManager function onNewDeviceVerificationRequest(flow,transactionId,userId,deviceId,isRequest) { - flow.userId = userId; - flow.sender = false; - flow.deviceId = deviceId; - switch(flow.type){ - case DeviceVerificationFlow.ToDevice: - flow.tranId = transactionId; - deviceVerificationList.add(flow.tranId); - break; - case DeviceVerificationFlow.RoomMsg: - deviceVerificationList.add(flow.tranId); - break; - } - var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow,isRequest: isRequest,tran_id: flow.tranId}); + var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow}); dialog.show(); } } diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index dc6bc16..e7dcc77 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -15,24 +15,12 @@ ApplicationWindow{ width: 420 minimumHeight: 420 - modality: Qt.WindowModal palette: colors - Connections{ - target: deviceVerificationList - function onUpdateProfile() { - profile.fetchDeviceList(profile.userid) - } - } - Component { id: deviceVerificationDialog DeviceVerification {} } - Component{ - id: deviceVerificationFlow - DeviceVerificationFlow {} - } ColumnLayout{ id: contentL @@ -73,14 +61,7 @@ ApplicationWindow{ enabled: !profile.isUserVerified visible: !profile.isUserVerified - onClicked: { - var newFlow = profile.createFlow(true); - newFlow.userId = profile.userid; - newFlow.sender = true; - deviceVerificationList.add(newFlow.tranId); - var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest: true,tran_id: newFlow.tranId}); - dialog.show(); - } + onClicked: profile.verify() } RowLayout { @@ -172,17 +153,11 @@ ApplicationWindow{ id: verifyButton text: (model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify" onClicked: { - var newFlow = profile.createFlow(false); - newFlow.userId = profile.userid; - newFlow.sender = true; - newFlow.deviceId = model.deviceId; if(model.verificationStatus == VerificationStatus.VERIFIED){ - newFlow.unverify(); + profile.unverify(model.deviceId) deviceVerificationList.updateProfile(newFlow.userId); }else{ - deviceVerificationList.add(newFlow.tranId); - var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow,isRequest:false,tran_id: newFlow.tranId}); - dialog.show(); + profile.verify(model.deviceId); } } } diff --git a/resources/qml/device-verification/AcceptNewVerificationRequest.qml b/resources/qml/device-verification/AcceptNewVerificationRequest.qml deleted file mode 100644 index 5bdbc4a..0000000 --- a/resources/qml/device-verification/AcceptNewVerificationRequest.qml +++ /dev/null @@ -1,49 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 2.10 -import QtQuick.Layouts 1.10 - -import im.nheko 1.0 - -Pane { - property string title: qsTr("Recieving Device Verification Request") - Component { - id: awaitingVerificationRequestAccept - AwaitingVerificationRequest {} - } - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("The device was requested to be verified") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("Deny") - - onClicked: { - flow.cancelVerification(DeviceVerificationFlow.User); - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("Accept") - - onClicked: { - stack.replace(awaitingVerificationRequestAccept); - flow.sender ?flow.sendVerificationReady():flow.acceptVerificationRequest(); - } - } - } - } -} diff --git a/resources/qml/device-verification/AwaitingVerificationConfirmation.qml b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml index aaebba6..cd8ccfd 100644 --- a/resources/qml/device-verification/AwaitingVerificationConfirmation.qml +++ b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml @@ -27,9 +27,8 @@ Pane { text: qsTr("Cancel") onClicked: { - flow.cancelVerification(DeviceVerificationFlow.User); - deviceVerificationList.remove(tran_id); - dialog.destroy(); + flow.cancel(); + dialog.close(); } } Item { diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index ca98098..4e93df0 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -18,36 +18,31 @@ ApplicationWindow { height: stack.implicitHeight width: stack.implicitWidth - Component{ - id: newVerificationRequest - NewVerificationRequest {} - } - - Component{ - id: acceptNewVerificationRequest - AcceptNewVerificationRequest {} - } - StackView { id: stack - initialItem: flow.sender == true?newVerificationRequest:acceptNewVerificationRequest + initialItem: newVerificationRequest implicitWidth: currentItem.implicitWidth implicitHeight: currentItem.implicitHeight } + Component{ + id: newVerificationRequest + NewVerificationRequest {} + } + Component { - id: partnerAborted - PartnerAborted {} + id: waiting + Waiting {} } Component { - id: timedout - TimedOut {} + id: success + Success {} } Component { - id: verificationSuccess - VerificationSuccess {} + id: failed + Failed {} } Component { @@ -60,19 +55,42 @@ ApplicationWindow { EmojiVerification {} } - Connections { - target: flow - onVerificationCanceled: stack.replace(partnerAborted) - onTimedout: stack.replace(timedout) - onDeviceVerified: stack.replace(verificationSuccess) - - onVerificationRequestAccepted: switch(method) { - case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break; - case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break; + Item { + state: flow.state + + states: [ + State { + name: "PromptStartVerification" + StateChangeScript { script: stack.replace(newVerificationRequest) } + }, + State { + name: "CompareEmoji" + StateChangeScript { script: stack.replace(emojiVerification) } + }, + State { + name: "CompareNumber" + StateChangeScript { script: stack.replace(digitVerification) } + }, + State { + name: "WaitingForKeys" + StateChangeScript { script: stack.replace(waiting) } + }, + State { + name: "WaitingForOtherToAccept" + StateChangeScript { script: stack.replace(waiting) } + }, + State { + name: "WaitingForMac" + StateChangeScript { script: stack.replace(waiting) } + }, + State { + name: "Success" + StateChangeScript { script: stack.replace(success) } + }, + State { + name: "Failed" + StateChangeScript { script: stack.replace(failed); } } - - onRefreshProfile: { - deviceVerificationList.updateProfile(flow.userId); - } - } + ] +} } diff --git a/resources/qml/device-verification/DigitVerification.qml b/resources/qml/device-verification/DigitVerification.qml index f3b1f5c..ff878a5 100644 --- a/resources/qml/device-verification/DigitVerification.qml +++ b/resources/qml/device-verification/DigitVerification.qml @@ -6,10 +6,7 @@ import im.nheko 1.0 Pane { property string title: qsTr("Verification Code") - Component { - id: awaitingVerificationConfirmation - AwaitingVerificationConfirmation {} - } + ColumnLayout { spacing: 16 Label { @@ -45,9 +42,8 @@ Pane { text: qsTr("They do not match!") onClicked: { - flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); - deviceVerificationList.remove(tran_id); - dialog.destroy(); + flow.cancel(); + dialog.close(); } } Item { @@ -57,7 +53,7 @@ Pane { Layout.alignment: Qt.AlignRight text: qsTr("They match!") - onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } + onClicked: flow.next(); } } } diff --git a/resources/qml/device-verification/EmojiVerification.qml b/resources/qml/device-verification/EmojiVerification.qml index 19faf1b..ed7727a 100644 --- a/resources/qml/device-verification/EmojiVerification.qml +++ b/resources/qml/device-verification/EmojiVerification.qml @@ -6,10 +6,7 @@ import im.nheko 1.0 Pane { property string title: qsTr("Verification Code") - Component { - id: awaitingVerificationConfirmation - AwaitingVerificationConfirmation{} - } + ColumnLayout { spacing: 16 Label { @@ -125,9 +122,8 @@ Pane { text: qsTr("They do not match!") onClicked: { - flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); - deviceVerificationList.remove(tran_id); - dialog.destroy(); + flow.cancel(); + dialog.close(); } } Item { @@ -137,7 +133,7 @@ Pane { Layout.alignment: Qt.AlignRight text: qsTr("They match!") - onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } + onClicked: flow.next() } } } diff --git a/resources/qml/device-verification/TimedOut.qml b/resources/qml/device-verification/Failed.qml similarity index 50% rename from resources/qml/device-verification/TimedOut.qml rename to resources/qml/device-verification/Failed.qml index 7dd0ab6..6b5d57e 100644 --- a/resources/qml/device-verification/TimedOut.qml +++ b/resources/qml/device-verification/Failed.qml @@ -12,7 +12,14 @@ Pane { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: qsTr("Device verification timed out.") + text: switch (flow.error) { + case VerificationStatus.UnknownMethod: return qsTr("Device verification timed out.") + case VerificationStatus.MismatchedCommitment: return qsTr("Device verification timed out.") + case VerificationStatus.MismatchedSAS: return qsTr("Device verification timed out.") + case VerificationStatus.KeyMismatch: return qsTr("Device verification timed out.") + case VerificationStatus.Timeout: return qsTr("Device verification timed out.") + case VerificationStatus.OutOfOrder: return qsTr("Device verification timed out.") + } color:colors.text verticalAlignment: Text.AlignVCenter } @@ -27,7 +34,7 @@ Pane { onClicked: { deviceVerificationList.remove(tran_id); flow.deleteFlow(); - dialog.destroy() + dialog.close() } } } diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index ef730b1..bd25bb9 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -2,12 +2,11 @@ import QtQuick 2.3 import QtQuick.Controls 2.10 import QtQuick.Layouts 1.10 +import im.nheko 1.0 + Pane { - property string title: qsTr("Sending Device Verification Request") - Component { - id: awaitingVerificationRequestAccept - AwaitingVerificationRequest {} - } + property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request") + ColumnLayout { spacing: 16 Label { @@ -15,28 +14,20 @@ Pane { Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.Wrap - text: qsTr("A new device was added.") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device.") + text: flow.sender ? + qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") + : qsTr("The device was requested to be verified") color:colors.text verticalAlignment: Text.AlignVCenter } RowLayout { Button { Layout.alignment: Qt.AlignLeft - text: qsTr("Cancel") + text: flow.sender ? qsTr("Cancel") : qsTr("Deny") onClicked: { - deviceVerificationList.remove(tran_id); - flow.deleteFlow(); - dialog.destroy(); + flow.cancel(); + dialog.close(); } } Item { @@ -44,12 +35,10 @@ Pane { } Button { Layout.alignment: Qt.AlignRight - text: qsTr("Start verification") + text: flow.sender ? qsTr("Start verification") : qsTr("Accept") - onClicked: { - stack.replace(awaitingVerificationRequestAccept); - flow.sender ?flow.sendVerificationRequest():flow.startVerificationRequest(); } - } + onClicked: flow.next(); } } } +} diff --git a/resources/qml/device-verification/PartnerAborted.qml b/resources/qml/device-verification/PartnerAborted.qml deleted file mode 100644 index 6174477..0000000 --- a/resources/qml/device-verification/PartnerAborted.qml +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 2.10 -import QtQuick.Layouts 1.10 - -Pane { - property string title: qsTr("Verification aborted!") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - id: content - text: qsTr("Verification canceled by the other party!") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - RowLayout { - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("Close") - - onClicked: { - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - } - } -} diff --git a/resources/qml/device-verification/VerificationSuccess.qml b/resources/qml/device-verification/Success.qml similarity index 82% rename from resources/qml/device-verification/VerificationSuccess.qml rename to resources/qml/device-verification/Success.qml index bc1e64f..b17b293 100644 --- a/resources/qml/device-verification/VerificationSuccess.qml +++ b/resources/qml/device-verification/Success.qml @@ -24,11 +24,7 @@ Pane { Layout.alignment: Qt.AlignRight text: qsTr("Close") - onClicked: { - deviceVerificationList.remove(tran_id); - if(flow) flow.deleteFlow(); - dialog.destroy(); - } + onClicked: dialog.close(); } } } diff --git a/resources/qml/device-verification/AwaitingVerificationRequest.qml b/resources/qml/device-verification/Waiting.qml similarity index 59% rename from resources/qml/device-verification/AwaitingVerificationRequest.qml rename to resources/qml/device-verification/Waiting.qml index b4b9178..f36910e 100644 --- a/resources/qml/device-verification/AwaitingVerificationRequest.qml +++ b/resources/qml/device-verification/Waiting.qml @@ -14,12 +14,18 @@ Pane { Layout.fillWidth: true wrapMode: Text.Wrap id: content - text: qsTr("Waiting for other side to accept the verification request.") + text: switch (flow.state) { + case "WaitingForOtherToAccept": return qsTr("Waiting for other side to accept the verification request.") + case "WaitingForKeys": return qsTr("Waiting for other side to continue the verification request.") + case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.") + } + color:colors.text verticalAlignment: Text.AlignVCenter } BusyIndicator { Layout.alignment: Qt.AlignHCenter + palette: color } RowLayout { Button { @@ -27,9 +33,8 @@ Pane { text: qsTr("Cancel") onClicked: { - flow.cancelVerification(DeviceVerificationFlow.User); - deviceVerificationList.remove(tran_id); - dialog.destroy(); + flow.cancel(); + dialog.close(); } } Item { diff --git a/resources/res.qrc b/resources/res.qrc index 7ef7ecf..64e5b08 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -141,16 +141,13 @@ qml/delegates/Pill.qml qml/delegates/Placeholder.qml qml/delegates/Reply.qml - qml/device-verification/AcceptNewVerificationRequest.qml - qml/device-verification/AwaitingVerificationConfirmation.qml - qml/device-verification/AwaitingVerificationRequest.qml + qml/device-verification/Waiting.qml qml/device-verification/DeviceVerification.qml qml/device-verification/DigitVerification.qml qml/device-verification/EmojiVerification.qml qml/device-verification/NewVerificationRequest.qml - qml/device-verification/PartnerAborted.qml - qml/device-verification/TimedOut.qml - qml/device-verification/VerificationSuccess.qml + qml/device-verification/Failed.qml + qml/device-verification/Success.qml media/ring.ogg diff --git a/src/ChatPage.h b/src/ChatPage.h index f363c4f..c37aa91 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -169,22 +169,22 @@ signals: void decryptSidebarChanged(); //! Signals for device verificaiton - void recievedDeviceVerificationAccept( + void receivedDeviceVerificationAccept( const mtx::events::msg::KeyVerificationAccept &message); - void recievedDeviceVerificationRequest( + void receivedDeviceVerificationRequest( const mtx::events::msg::KeyVerificationRequest &message, std::string sender); - void recievedRoomDeviceVerificationRequest( + void receivedRoomDeviceVerificationRequest( const mtx::events::RoomEvent &message, TimelineModel *model); - void recievedDeviceVerificationCancel( + void receivedDeviceVerificationCancel( const mtx::events::msg::KeyVerificationCancel &message); - void recievedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message); - void recievedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message); - void recievedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message, + void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message); + void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message); + void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message, std::string sender); - void recievedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message); - void recievedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message); + void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message); + void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message); private slots: void showUnreadMessageNotification(int count); diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 7b367de..99fd7be 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -15,8 +15,12 @@ namespace msgs = mtx::events::msg; DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type, - TimelineModel *model) - : type(flow_type) + TimelineModel *model, + QString userID, + QString deviceId_) + : sender(false) + , type(flow_type) + , deviceId(deviceId_) , model_(model) { timeout = new QTimer(this); @@ -24,6 +28,30 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, this->sas = olm::client()->sas_init(); this->isMacVerified = false; + auto user_id = userID.toStdString(); + this->toClient = mtx::identifiers::parse(user_id); + ChatPage::instance()->query_keys( + user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + + if (!this->deviceId.isEmpty() && + (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { + nhlog::net()->warn("no devices retrieved {}", user_id); + return; + } + + for (const auto &[algorithm, key] : + res.device_keys.at(deviceId.toStdString()).keys) { + // TODO: Verify Signatures + this->device_keys[algorithm] = key; + } + }); + if (model) { connect(this->model_, &TimelineModel::updateFlowEventId, @@ -36,64 +64,15 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } connect(timeout, &QTimer::timeout, this, [this]() { - emit timedout(); this->cancelVerification(DeviceVerificationFlow::Error::Timeout); - this->deleteLater(); }); - connect(this, &DeviceVerificationFlow::deleteFlow, this, [this]() { this->deleteLater(); }); - - connect( - ChatPage::instance(), - &ChatPage::recievedDeviceVerificationStart, - this, - [this](const mtx::events::msg::KeyVerificationStart &msg, std::string) { - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relates_to.has_value()) { - if (msg.relates_to.value().event_id != this->relation.event_id) - return; - } - if ((std::find(msg.key_agreement_protocols.begin(), - msg.key_agreement_protocols.end(), - "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) && - (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != - msg.hashes.end()) && - (std::find(msg.message_authentication_codes.begin(), - msg.message_authentication_codes.end(), - "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) { - if (std::find(msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Decimal) != - msg.short_authentication_string.end()) { - this->method = DeviceVerificationFlow::Method::Emoji; - } else if (std::find(msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.short_authentication_string.end()) { - this->method = DeviceVerificationFlow::Method::Decimal; - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - return; - } - if (!sender) - this->canonical_json = nlohmann::json(msg); - else { - if (utils::localUser().toStdString() < - this->toClient.to_string()) { - this->canonical_json = nlohmann::json(msg); - } - } - this->acceptVerificationRequest(); - } else { - this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); - } - }); - connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationAccept, + &ChatPage::receivedDeviceVerificationStart, + this, + &DeviceVerificationFlow::handleStartMessage); + connect(ChatPage::instance(), + &ChatPage::receivedDeviceVerificationAccept, this, [this](const mtx::events::msg::KeyVerificationAccept &msg) { if (msg.transaction_id.has_value()) { @@ -111,9 +90,9 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, msg.short_authentication_string.end(), mtx::events::msg::SASMethods::Emoji) != msg.short_authentication_string.end()) { - this->method = DeviceVerificationFlow::Method::Emoji; + this->method = mtx::events::msg::SASMethods::Emoji; } else { - this->method = DeviceVerificationFlow::Method::Decimal; + this->method = mtx::events::msg::SASMethods::Decimal; } this->mac_method = msg.message_authentication_code; this->sendVerificationKey(); @@ -124,7 +103,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, }); connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationCancel, + &ChatPage::receivedDeviceVerificationCancel, this, [this](const mtx::events::msg::KeyVerificationCancel &msg) { if (msg.transaction_id.has_value()) { @@ -134,12 +113,13 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, if (msg.relates_to.value().event_id != this->relation.event_id) return; } - this->deleteLater(); - emit verificationCanceled(); + error_ = User; + emit errorChanged(); + setState(Failed); }); connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationKey, + &ChatPage::receivedDeviceVerificationKey, this, [this](const mtx::events::msg::KeyVerificationKey &msg) { if (msg.transaction_id.has_value()) { @@ -149,6 +129,19 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, if (msg.relates_to.value().event_id != this->relation.event_id) return; } + + if (sender) { + if (state_ != WaitingForOtherToAccept) { + this->cancelVerification(OutOfOrder); + return; + } + } else { + if (state_ != WaitingForKeys) { + this->cancelVerification(OutOfOrder); + return; + } + } + this->sas->set_their_key(msg.key); std::string info; if (this->sender == true) { @@ -166,31 +159,30 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, "|" + this->transaction_id; } - if (this->method == DeviceVerificationFlow::Method::Emoji) { - std::cout << info << std::endl; - this->sasList = this->sas->generate_bytes_emoji(info); - } else if (this->method == DeviceVerificationFlow::Method::Decimal) { - this->sasList = this->sas->generate_bytes_decimal(info); - } - if (this->sender == false) { - emit this->verificationRequestAccepted(this->method); this->sendVerificationKey(); } else { - if (this->commitment == + if (this->commitment != mtx::crypto::bin2base64_unpadded( mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) { - emit this->verificationRequestAccepted(this->method); - } else { this->cancelVerification( DeviceVerificationFlow::Error::MismatchedCommitment); + return; } } + + if (this->method == mtx::events::msg::SASMethods::Emoji) { + this->sasList = this->sas->generate_bytes_emoji(info); + setState(CompareEmoji); + } else if (this->method == mtx::events::msg::SASMethods::Decimal) { + this->sasList = this->sas->generate_bytes_decimal(info); + setState(CompareNumber); + } }); connect( ChatPage::instance(), - &ChatPage::recievedDeviceVerificationMac, + &ChatPage::receivedDeviceVerificationMac, this, [this](const mtx::events::msg::KeyVerificationMac &msg) { if (msg.transaction_id.has_value()) { @@ -222,26 +214,22 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } key_string = key_string.substr(0, key_string.length() - 1); if (msg.keys == this->sas->calculate_mac(key_string, info + "KEY_IDS")) { - // uncomment this in future to be compatible with the - // MSC2366 this->sendVerificationDone(); and remove the - // below line - if (this->isMacVerified == true) { - this->acceptDevice(); - } else - this->isMacVerified = true; + this->isMacVerified = true; + this->acceptDevice(); } else { this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch); } }); connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationReady, + &ChatPage::receivedDeviceVerificationReady, this, [this](const mtx::events::msg::KeyVerificationReady &msg) { if (!sender) { if (msg.from_device != http::client()->device_id()) { - this->deleteLater(); - emit verificationCanceled(); + error_ = User; + emit errorChanged(); + setState(Failed); } return; @@ -261,7 +249,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, }); connect(ChatPage::instance(), - &ChatPage::recievedDeviceVerificationDone, + &ChatPage::receivedDeviceVerificationDone, this, [this](const mtx::events::msg::KeyVerificationDone &msg) { if (msg.transaction_id.has_value()) { @@ -271,22 +259,85 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, if (msg.relates_to.value().event_id != this->relation.event_id) return; } - this->acceptDevice(); + nhlog::ui()->info("Flow done on other side"); }); timeout->start(TIMEOUT); } QString -DeviceVerificationFlow::getTransactionId() +DeviceVerificationFlow::state() { - return QString::fromStdString(this->transaction_id); + switch (state_) { + case PromptStartVerification: + return "PromptStartVerification"; + case CompareEmoji: + return "CompareEmoji"; + case CompareNumber: + return "CompareNumber"; + case WaitingForKeys: + return "WaitingForKeys"; + case WaitingForOtherToAccept: + return "WaitingForOtherToAccept"; + case WaitingForMac: + return "WaitingForMac"; + case Success: + return "Success"; + case Failed: + return "Failed"; + default: + return ""; + } +} + +void +DeviceVerificationFlow::next() +{ + if (sender) { + switch (state_) { + case PromptStartVerification: + sendVerificationRequest(); + break; + case CompareEmoji: + case CompareNumber: + sendVerificationMac(); + break; + case WaitingForKeys: + case WaitingForOtherToAccept: + case WaitingForMac: + case Success: + case Failed: + nhlog::db()->error("verification: Invalid state transition!"); + break; + } + } else { + switch (state_) { + case PromptStartVerification: + if (canonical_json.is_null()) + sendVerificationReady(); + else // legacy path without request and ready + acceptVerificationRequest(); + break; + case CompareEmoji: + [[fallthrough]]; + case CompareNumber: + sendVerificationMac(); + break; + case WaitingForKeys: + case WaitingForOtherToAccept: + case WaitingForMac: + case Success: + case Failed: + nhlog::db()->error("verification: Invalid state transition!"); + break; + } + } } QString DeviceVerificationFlow::getUserId() { - return this->userId; + return QString::fromStdString(this->toClient.to_string()); } QString @@ -295,18 +346,6 @@ DeviceVerificationFlow::getDeviceId() return this->deviceId; } -DeviceVerificationFlow::Method -DeviceVerificationFlow::getMethod() -{ - return this->method; -} - -DeviceVerificationFlow::Type -DeviceVerificationFlow::getType() -{ - return this->type; -} - bool DeviceVerificationFlow::getSender() { @@ -320,56 +359,58 @@ DeviceVerificationFlow::getSasList() } void -DeviceVerificationFlow::setTransactionId(QString transaction_id_) -{ - this->transaction_id = transaction_id_.toStdString(); -} - -void -DeviceVerificationFlow::setUserId(QString userID) -{ - this->userId = userID; - this->toClient = mtx::identifiers::parse(userID.toStdString()); - - auto user_id = userID.toStdString(); - ChatPage::instance()->query_keys( - user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { - this->callback_fn(res, err, user_id); - }); -} - -void -DeviceVerificationFlow::setDeviceId(QString deviceID) -{ - this->deviceId = deviceID; -} - -void -DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_) -{ - this->method = method_; -} - -void -DeviceVerificationFlow::setType(Type type_) +DeviceVerificationFlow::setEventId(std::string event_id_) { - this->type = type_; + this->relation.rel_type = mtx::common::RelationType::Reference; + this->relation.event_id = event_id_; + this->transaction_id = event_id_; } void -DeviceVerificationFlow::setSender(bool sender_) +DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, + std::string) { - this->sender = sender_; - if (this->sender) - this->transaction_id = http::client()->generate_txn_id(); -} + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().event_id != this->relation.event_id) + return; + } + if ((std::find(msg.key_agreement_protocols.begin(), + msg.key_agreement_protocols.end(), + "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) && + (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) && + (std::find(msg.message_authentication_codes.begin(), + msg.message_authentication_codes.end(), + "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) { + if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), + mtx::events::msg::SASMethods::Emoji) != + msg.short_authentication_string.end()) { + this->method = mtx::events::msg::SASMethods::Emoji; + } else if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), + mtx::events::msg::SASMethods::Decimal) != + msg.short_authentication_string.end()) { + this->method = mtx::events::msg::SASMethods::Decimal; + } else { + this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); + return; + } + if (!sender) + this->canonical_json = nlohmann::json(msg); + else { + if (utils::localUser().toStdString() < this->toClient.to_string()) { + this->canonical_json = nlohmann::json(msg); + } + } -void -DeviceVerificationFlow::setEventId(std::string event_id_) -{ - this->relation.rel_type = mtx::common::RelationType::Reference; - this->relation.event_id = event_id_; - this->transaction_id = event_id_; + if (state_ != PromptStartVerification) + this->acceptVerificationRequest(); + } else { + this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); + } } //! accepts a verification @@ -382,30 +423,15 @@ DeviceVerificationFlow::acceptVerificationRequest() req.key_agreement_protocol = "curve25519-hkdf-sha256"; req.hash = "sha256"; req.message_authentication_code = "hkdf-hmac-sha256"; - if (this->method == DeviceVerificationFlow::Method::Emoji) + if (this->method == mtx::events::msg::SASMethods::Emoji) req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji}; - else if (this->method == DeviceVerificationFlow::Method::Decimal) + else if (this->method == mtx::events::msg::SASMethods::Decimal) req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal}; req.commitment = mtx::crypto::bin2base64_unpadded( mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump())); - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - mtx::requests::ToDeviceMessages body; - req.transaction_id = this->transaction_id; - - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to accept verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relates_to = this->relation; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationAccept); - } + send(req); + setState(WaitingForKeys); } //! responds verification request void @@ -416,23 +442,8 @@ DeviceVerificationFlow::sendVerificationReady() req.from_device = http::client()->device_id(); req.methods = {mtx::events::msg::VerificationMethods::SASv1}; - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - req.transaction_id = this->transaction_id; - mtx::requests::ToDeviceMessages body; - - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification ready: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relates_to = this->relation; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationReady); - } + send(req); + setState(WaitingForKeys); } //! accepts a verification void @@ -440,23 +451,7 @@ DeviceVerificationFlow::sendVerificationDone() { mtx::events::msg::KeyVerificationDone req; - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - mtx::requests::ToDeviceMessages body; - req.transaction_id = this->transaction_id; - - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification done: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relates_to = this->relation; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationDone); - } + send(req); } //! starts the verification flow void @@ -474,22 +469,14 @@ DeviceVerificationFlow::startVerificationRequest() if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; - req.transaction_id = this->transaction_id; - this->canonical_json = nlohmann::json(req); - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [body](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to start verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + req.transaction_id = this->transaction_id; + this->canonical_json = nlohmann::json(req); } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { req.relates_to = this->relation; this->canonical_json = nlohmann::json(req); - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationStart); } + send(req); + setState(WaitingForOtherToAccept); } //! sends a verification request void @@ -503,28 +490,18 @@ DeviceVerificationFlow::sendVerificationRequest() if (this->type == DeviceVerificationFlow::Type::ToDevice) { QDateTime currentTime = QDateTime::currentDateTimeUtc(); - req.transaction_id = this->transaction_id; - req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch(); - - mtx::requests::ToDeviceMessages body; + req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch(); - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.to = this->userId.toStdString(); + req.to = this->toClient.to_string(); req.msgtype = "m.key.verification.request"; req.body = "User is requesting to verify keys with you. However, your client does " "not support this method, so you will need to use the legacy method of " "key verification."; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationRequest); } + + send(req); + setState(WaitingForOtherToAccept); } //! cancels a verification flow void @@ -534,7 +511,7 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c if (error_code == DeviceVerificationFlow::Error::UnknownMethod) { req.code = "m.unknown_method"; - req.reason = "unknown method recieved"; + req.reason = "unknown method received"; } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) { req.code = "m.mismatched_commitment"; req.reason = "commitment didn't match"; @@ -550,42 +527,16 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c } else if (error_code == DeviceVerificationFlow::Error::User) { req.code = "m.user"; req.reason = "user cancelled the verification"; + } else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) { + req.code = "m.unexpected_message"; + req.reason = "received messages out of order"; } - emit this->verificationCanceled(); + this->error_ = error_code; + emit errorChanged(); + this->setState(Failed); - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - req.transaction_id = this->transaction_id; - mtx::requests::ToDeviceMessages body; - - body[this->toClient][deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [this](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to cancel verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - - this->deleteLater(); - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relates_to = this->relation; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationCancel); - this->deleteLater(); - } - - // TODO : Handle Blocking user better - // auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); - // if (verified_cache.has_value()) { - // verified_cache->device_blocked.push_back(this->deviceId.toStdString()); - // cache::setVerifiedCache(this->userId.toStdString(), - // verified_cache.value()); - // } else { - // cache::setVerifiedCache( - // this->userId.toStdString(), - // DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}}); - // } + send(req); } //! sends the verification key void @@ -595,23 +546,7 @@ DeviceVerificationFlow::sendVerificationKey() req.key = this->sas->public_key(); - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - mtx::requests::ToDeviceMessages body; - req.transaction_id = this->transaction_id; - - body[this->toClient][deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification key: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relates_to = this->relation; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationKey); - } + send(req); } mtx::events::msg::KeyVerificationMac @@ -660,68 +595,102 @@ DeviceVerificationFlow::sendVerificationMac() this->transaction_id, key_list); - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - mtx::requests::ToDeviceMessages body; - req.transaction_id = this->transaction_id; - body[this->toClient][deviceId.toStdString()] = req; - - http::client()->send_to_device( - this->transaction_id, body, [this](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification MAC: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - - if (this->isMacVerified == true) - this->acceptDevice(); - else - this->isMacVerified = true; - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relates_to = this->relation; - (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationMac); - } + send(req); + + setState(WaitingForMac); + acceptDevice(); } //! Completes the verification flow void DeviceVerificationFlow::acceptDevice() { - cache::markDeviceVerified(this->userId.toStdString(), this->deviceId.toStdString()); + if (!isMacVerified) { + setState(WaitingForMac); + } else if (state_ == WaitingForMac) { + cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString()); + this->sendVerificationDone(); + setState(Success); + } +} + +void +DeviceVerificationFlow::unverify() +{ + cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString()); - emit deviceVerified(); emit refreshProfile(); - this->deleteLater(); } -//! callback function to keep track of devices -void -DeviceVerificationFlow::callback_fn(const UserKeyCache &res, - mtx::http::RequestErr err, - std::string user_id) +QSharedPointer +DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, + TimelineModel *timelineModel_, + const mtx::events::msg::KeyVerificationRequest &msg, + QString other_user_, + QString event_id_) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - err->matrix_error.errcode, - static_cast(err->status_code)); - return; - } + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, other_user_, "")); + + flow->event_id = event_id_.toStdString(); - if (res.device_keys.empty() || - (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; + if (std::find(msg.methods.begin(), + msg.methods.end(), + mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { + flow->cancelVerification(UnknownMethod); } - for (const auto &[algorithm, key] : res.device_keys.at(deviceId.toStdString()).keys) { - // TODO: Verify Signatures - this->device_keys[algorithm] = key; + return flow; +} +QSharedPointer +DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, + const mtx::events::msg::KeyVerificationRequest &msg, + QString other_user_, + QString txn_id_) +{ + QSharedPointer flow(new DeviceVerificationFlow( + parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + flow->transaction_id = txn_id_.toStdString(); + + if (std::find(msg.methods.begin(), + msg.methods.end(), + mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { + flow->cancelVerification(UnknownMethod); } + + return flow; } +QSharedPointer +DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, + const mtx::events::msg::KeyVerificationStart &msg, + QString other_user_, + QString txn_id_) +{ + QSharedPointer flow(new DeviceVerificationFlow( + parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + flow->transaction_id = txn_id_.toStdString(); -void -DeviceVerificationFlow::unverify() + flow->handleStartMessage(msg, ""); + + return flow; +} +QSharedPointer +DeviceVerificationFlow::InitiateUserVerification(QObject *parent_, + TimelineModel *timelineModel_, + QString userid) { - cache::markDeviceUnverified(this->userId.toStdString(), this->deviceId.toStdString()); + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); + flow->sender = true; + return flow; +} +QSharedPointer +DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device) +{ + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); - emit refreshProfile(); + flow->sender = true; + flow->transaction_id = http::client()->generate_txn_id(); + + return flow; } diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index de7a456..1fe3919 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -5,41 +5,84 @@ #include #include "CacheCryptoStructs.h" +#include "Logging.h" #include "MatrixClient.h" #include "Olm.h" +#include "timeline/TimelineModel.h" class QTimer; using sas_ptr = std::unique_ptr; -class TimelineModel; - +// clang-format off +/* + * Stolen from fluffy chat :D + * + * State | +-------------+ +-----------+ | + * | | AliceDevice | | BobDevice | | + * | | (sender) | | | | + * | +-------------+ +-----------+ | + * promptStartVerify | | | | + * | o | (m.key.verification.request) | | + * | p |-------------------------------->| (ASK FOR VERIFICATION REQUEST) | + * waitForOtherAccept | t | | | promptStartVerify + * && | i | (m.key.verification.ready) | | + * no commitment | o |<--------------------------------| | + * && | n | | | + * no canonical_json | a | (m.key.verification.start) | | waitingForKeys + * | l |<--------------------------------| Not sending to prevent the glare resolve| && no commitment + * | | | | && no canonical_json + * | | m.key.verification.start | | + * waitForOtherAccept | |-------------------------------->| (IF NOT ALREADY ASKED, | + * && | | | ASK FOR VERIFICATION REQUEST) | promptStartVerify, if not accepted + * canonical_json | | m.key.verification.accept | | + * | |<--------------------------------| | + * waitForOtherAccept | | | | waitingForKeys + * && | | m.key.verification.key | | && canonical_json + * commitment | |-------------------------------->| | && commitment + * | | | | + * | | m.key.verification.key | | + * | |<--------------------------------| | + * compareEmoji/Number| | | | compareEmoji/Number + * | | COMPARE EMOJI / NUMBERS | | + * | | | | + * waitingForMac | | m.key.verification.mac | | waitingForMac + * | success |<------------------------------->| success | + * | | | | + * success/fail | | m.key.verification.done | | success/fail + * | |<------------------------------->| | + */ +// clang-format on class DeviceVerificationFlow : public QObject { Q_OBJECT // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") - Q_PROPERTY(QString tranId READ getTransactionId WRITE setTransactionId) - Q_PROPERTY(bool sender READ getSender WRITE setSender) - Q_PROPERTY(QString userId READ getUserId WRITE setUserId) - Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId) - Q_PROPERTY(Method method READ getMethod WRITE setMethod) - Q_PROPERTY(Type type READ getType WRITE setType) + Q_PROPERTY(QString state READ state NOTIFY stateChanged) + Q_PROPERTY(Error error READ error CONSTANT) + Q_PROPERTY(QString userId READ getUserId CONSTANT) + Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT) + Q_PROPERTY(bool sender READ getSender CONSTANT) Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) public: - enum Type + enum State { - ToDevice, - RoomMsg + PromptStartVerification, + WaitingForOtherToAccept, + WaitingForKeys, + CompareEmoji, + CompareNumber, + WaitingForMac, + Success, + Failed, }; - Q_ENUM(Type) + Q_ENUM(State) - enum Method + enum Type { - Decimal, - Emoji + ToDevice, + RoomMsg }; - Q_ENUM(Method) enum Error { @@ -48,36 +91,75 @@ public: MismatchedSAS, KeyMismatch, Timeout, - User + User, + OutOfOrder, }; Q_ENUM(Error) - DeviceVerificationFlow( - QObject *parent = nullptr, - DeviceVerificationFlow::Type = DeviceVerificationFlow::Type::ToDevice, - TimelineModel *model = nullptr); + static QSharedPointer NewInRoomVerification( + QObject *parent_, + TimelineModel *timelineModel_, + const mtx::events::msg::KeyVerificationRequest &msg, + QString other_user_, + QString event_id_); + static QSharedPointer NewToDeviceVerification( + QObject *parent_, + const mtx::events::msg::KeyVerificationRequest &msg, + QString other_user_, + QString txn_id_); + static QSharedPointer NewToDeviceVerification( + QObject *parent_, + const mtx::events::msg::KeyVerificationStart &msg, + QString other_user_, + QString txn_id_); + static QSharedPointer + InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); + static QSharedPointer InitiateDeviceVerification(QObject *parent, + QString userid, + QString device); + // getters - QString getTransactionId(); + QString state(); + Error error() { return error_; } QString getUserId(); QString getDeviceId(); - Method getMethod(); - Type getType(); - std::vector getSasList(); bool getSender(); + std::vector getSasList(); + QString transactionId() { return QString::fromStdString(this->transaction_id); } // setters - void setTransactionId(QString transaction_id_); - void setUserId(QString userID); void setDeviceId(QString deviceID); - void setMethod(Method method_); - void setType(Type type_); - void setSender(bool sender_); void setEventId(std::string event_id); void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); - nlohmann::json canonical_json; - public slots: + //! unverifies a device + void unverify(); + //! Continues the flow + void next(); + //! Cancel the flow + void cancel() { cancelVerification(User); } + +signals: + void refreshProfile(); + void stateChanged(); + void errorChanged(); + +private: + DeviceVerificationFlow(QObject *, + DeviceVerificationFlow::Type flow_type, + TimelineModel *model, + QString userID, + QString deviceId_); + void setState(State state) + { + if (state != state_) { + state_ = state; + emit stateChanged(); + } + } + + void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string); //! sends a verification request void sendVerificationRequest(); //! accepts a verification request @@ -96,37 +178,60 @@ public slots: void sendVerificationMac(); //! Completes the verification flow void acceptDevice(); - //! unverifies a device - void unverify(); -signals: - void verificationRequestAccepted(Method method); - void deviceVerified(); - void timedout(); - void verificationCanceled(); - void refreshProfile(); - void deleteFlow(); + // for to_device messages + std::string transaction_id; + // for room messages + std::optional room_id; + std::optional event_id; -private: - // general - QString userId; - QString deviceId; - Method method = Method::Emoji; - Type type; bool sender; - QTimer *timeout = nullptr; + Type type; + mtx::identifiers::User toClient; + QString deviceId; + + mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji; + QTimer *timeout = nullptr; sas_ptr sas; - bool isMacVerified = false; std::string mac_method; std::string commitment; - mtx::identifiers::User toClient; + nlohmann::json canonical_json; + std::vector sasList; std::map device_keys; - // for to_device messages - std::string transaction_id; - // for room messages - std::optional room_id; - std::optional event_id; TimelineModel *model_; mtx::common::RelatesTo relation; + + State state_ = PromptStartVerification; + Error error_; + + bool isMacVerified = false; + + template + void send(T msg) + { + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + msg.transaction_id = this->transaction_id; + body[this->toClient][deviceId.toStdString()] = msg; + + http::client()->send_to_device( + this->transaction_id, body, [this](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn( + "failed to send verification to_device message: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { + if constexpr (!std::is_same_v) + msg.relates_to = this->relation; + (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type); + } + + nhlog::net()->debug( + "Sent verification step: {} in state: {}", + mtx::events::to_string(mtx::events::to_device_content_to_type), + state().toStdString()); + } }; diff --git a/src/Olm.cpp b/src/Olm.cpp index 8219ce4..f4cb220 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -80,40 +80,40 @@ handle_to_device_messages(const std::vector>(msg); - ChatPage::instance()->recievedDeviceVerificationAccept(message.content); + ChatPage::instance()->receivedDeviceVerificationAccept(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { auto message = std::get< mtx::events::DeviceEvent>(msg); - ChatPage::instance()->recievedDeviceVerificationRequest(message.content, + ChatPage::instance()->receivedDeviceVerificationRequest(message.content, message.sender); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { auto message = std::get< mtx::events::DeviceEvent>(msg); - ChatPage::instance()->recievedDeviceVerificationCancel(message.content); + ChatPage::instance()->receivedDeviceVerificationCancel(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { auto message = std::get>( msg); - ChatPage::instance()->recievedDeviceVerificationKey(message.content); + ChatPage::instance()->receivedDeviceVerificationKey(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { auto message = std::get>( msg); - ChatPage::instance()->recievedDeviceVerificationMac(message.content); + ChatPage::instance()->receivedDeviceVerificationMac(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { auto message = std::get< mtx::events::DeviceEvent>(msg); - ChatPage::instance()->recievedDeviceVerificationStart(message.content, + ChatPage::instance()->receivedDeviceVerificationStart(message.content, message.sender); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { auto message = std::get< mtx::events::DeviceEvent>(msg); - ChatPage::instance()->recievedDeviceVerificationReady(message.content); + ChatPage::instance()->receivedDeviceVerificationReady(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { auto message = std::get>( msg); - ChatPage::instance()->recievedDeviceVerificationDone(message.content); + ChatPage::instance()->receivedDeviceVerificationDone(message.content); } else { nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); } @@ -153,42 +153,42 @@ handle_olm_message(const OlmMessage &msg) std::string msg_type = payload["type"]; if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) { - ChatPage::instance()->recievedDeviceVerificationAccept( + ChatPage::instance()->receivedDeviceVerificationAccept( payload["content"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { - ChatPage::instance()->recievedDeviceVerificationRequest( + ChatPage::instance()->receivedDeviceVerificationRequest( payload["content"], payload["sender"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { - ChatPage::instance()->recievedDeviceVerificationCancel( + ChatPage::instance()->receivedDeviceVerificationCancel( payload["content"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { - ChatPage::instance()->recievedDeviceVerificationKey( + ChatPage::instance()->receivedDeviceVerificationKey( payload["content"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { - ChatPage::instance()->recievedDeviceVerificationMac( + ChatPage::instance()->receivedDeviceVerificationMac( payload["content"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { - ChatPage::instance()->recievedDeviceVerificationStart( + ChatPage::instance()->receivedDeviceVerificationStart( payload["content"], payload["sender"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { - ChatPage::instance()->recievedDeviceVerificationReady( + ChatPage::instance()->receivedDeviceVerificationReady( payload["content"]); return; } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { - ChatPage::instance()->recievedDeviceVerificationDone( + ChatPage::instance()->receivedDeviceVerificationDone( payload["content"]); return; } else if (msg_type == to_string(mtx::events::EventType::RoomKey)) { diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 29b3c23..dc92a37 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -299,7 +299,7 @@ EventStore::handleSync(const mtx::responses::Timeline &events) mtx::events::msg::KeyVerificationReady>>(d_event)) { auto msg = std::get_if>(d_event); - ChatPage::instance()->recievedDeviceVerificationReady( + ChatPage::instance()->receivedDeviceVerificationReady( msg->content); } } @@ -328,43 +328,43 @@ EventStore::handle_room_verification(mtx::events::collections::TimelineEvents ev auto msg = std::get>(event); last_verification_cancel_event = msg; - ChatPage::instance()->recievedDeviceVerificationCancel(msg.content); + ChatPage::instance()->receivedDeviceVerificationCancel(msg.content); return; } else if (std::get_if>( &event)) { auto msg = std::get>(event); - ChatPage::instance()->recievedDeviceVerificationAccept(msg.content); + ChatPage::instance()->receivedDeviceVerificationAccept(msg.content); return; } else if (std::get_if>( &event)) { auto msg = std::get>(event); - ChatPage::instance()->recievedDeviceVerificationKey(msg.content); + ChatPage::instance()->receivedDeviceVerificationKey(msg.content); return; } else if (std::get_if>( &event)) { auto msg = std::get>(event); - ChatPage::instance()->recievedDeviceVerificationMac(msg.content); + ChatPage::instance()->receivedDeviceVerificationMac(msg.content); return; } else if (std::get_if>( &event)) { auto msg = std::get>(event); - ChatPage::instance()->recievedDeviceVerificationReady(msg.content); + ChatPage::instance()->receivedDeviceVerificationReady(msg.content); return; } else if (std::get_if>( &event)) { auto msg = std::get>(event); - ChatPage::instance()->recievedDeviceVerificationDone(msg.content); + ChatPage::instance()->receivedDeviceVerificationDone(msg.content); return; } else if (std::get_if>( &event)) { auto msg = std::get>(event); - ChatPage::instance()->recievedDeviceVerificationStart(msg.content, msg.sender); + ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender); return; } } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 5e8952f..e8d381d 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -254,7 +254,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj &EventStore::startDMVerification, this, [this](mtx::events::RoomEvent msg) { - ChatPage::instance()->recievedRoomDeviceVerificationRequest(msg, this); + ChatPage::instance()->receivedRoomDeviceVerificationRequest(msg, this); }); connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) { this->updateFlowEventId(event_id); @@ -793,7 +793,7 @@ TimelineModel::viewDecryptedRawMessage(QString id) const void TimelineModel::openUserProfile(QString userid) { - emit openProfile(new UserProfile(room_id_, userid, this)); + emit openProfile(new UserProfile(room_id_, userid, manager_, this)); } void diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 03dd477..250cd5f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -28,22 +28,6 @@ Q_DECLARE_METATYPE(std::vector) namespace msgs = mtx::events::msg; -void -DeviceVerificationList::add(QString tran_id) -{ - this->deviceVerificationList.push_back(tran_id); -} -void -DeviceVerificationList::remove(QString tran_id) -{ - this->deviceVerificationList.removeOne(tran_id); -} -bool -DeviceVerificationList::exist(QString tran_id) -{ - return this->deviceVerificationList.contains(tran_id); -} - void TimelineViewManager::updateEncryptedDescriptions() { @@ -134,7 +118,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin qmlRegisterType("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); - qmlRegisterType("im.nheko", 1, 0, "DeviceVerificationFlow"); + qmlRegisterUncreatableType( + "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); qmlRegisterUncreatableType( "im.nheko", 1, @@ -163,7 +148,6 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin 0, "EmojiCategory", "Error: Only enums"); - this->dvList = new DeviceVerificationList; #ifdef USE_QUICK_VIEW view = new QQuickView(); @@ -183,7 +167,6 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin }); #endif container->setMinimumSize(200, 200); - view->rootContext()->setContextProperty("deviceVerificationList", this->dvList); updateColorPalette(); view->engine()->addImageProvider("MxcImage", imgProvider); view->engine()->addImageProvider("colorimage", colorImgProvider); @@ -197,102 +180,55 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin &TimelineViewManager::updateEncryptedDescriptions); connect( dynamic_cast(parent), - &ChatPage::recievedRoomDeviceVerificationRequest, + &ChatPage::receivedRoomDeviceVerificationRequest, this, [this](const mtx::events::RoomEvent &message, TimelineModel *model) { - if (!(this->dvList->exist(QString::fromStdString(message.event_id)))) { - auto flow = new DeviceVerificationFlow( - this, DeviceVerificationFlow::Type::RoomMsg, model); - if (std::find(message.content.methods.begin(), - message.content.methods.end(), - mtx::events::msg::VerificationMethods::SASv1) != - message.content.methods.end()) { - flow->setEventId(message.event_id); - emit newDeviceVerificationRequest( - std::move(flow), - QString::fromStdString(message.event_id), - QString::fromStdString(message.sender), - QString::fromStdString(message.content.from_device), - true); - } else { - flow->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - } - } - }); - connect( - dynamic_cast(parent), - &ChatPage::recievedDeviceVerificationRequest, - this, - [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) { - if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) { - auto flow = new DeviceVerificationFlow(this); - if (std::find(msg.methods.begin(), - msg.methods.end(), - mtx::events::msg::VerificationMethods::SASv1) != - msg.methods.end()) { - emit newDeviceVerificationRequest( - std::move(flow), - QString::fromStdString(msg.transaction_id.value()), - QString::fromStdString(sender), - QString::fromStdString(msg.from_device)); - } else { - flow->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - } - } - }); - connect( - dynamic_cast(parent), - &ChatPage::recievedDeviceVerificationStart, - this, - [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) { - if (msg.transaction_id.has_value()) { - if (!(this->dvList->exist( - QString::fromStdString(msg.transaction_id.value())))) { - auto flow = new DeviceVerificationFlow(this); - flow->canonical_json = nlohmann::json(msg); - if ((std::find(msg.key_agreement_protocols.begin(), - msg.key_agreement_protocols.end(), - "curve25519-hkdf-sha256") != - msg.key_agreement_protocols.end()) && - (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != - msg.hashes.end()) && - (std::find(msg.message_authentication_codes.begin(), - msg.message_authentication_codes.end(), - "hmac-sha256") != - msg.message_authentication_codes.end())) { - if (std::find(msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.short_authentication_string.end()) { - flow->setMethod( - DeviceVerificationFlow::Method::Emoji); - } else if (std::find( - msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Decimal) != - msg.short_authentication_string.end()) { - flow->setMethod( - DeviceVerificationFlow::Method::Decimal); - } else { - flow->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - return; - } - emit newDeviceVerificationRequest( - std::move(flow), - QString::fromStdString(msg.transaction_id.value()), - QString::fromStdString(sender), - QString::fromStdString(msg.from_device)); - } else { - flow->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - } + auto event_id = QString::fromStdString(message.event_id); + if (!this->dvList.contains(event_id)) { + if (auto flow = DeviceVerificationFlow::NewInRoomVerification( + this, + model, + message.content, + QString::fromStdString(message.sender), + event_id)) { + dvList[event_id] = flow; + emit newDeviceVerificationRequest(flow.data()); } } }); + connect(dynamic_cast(parent), + &ChatPage::receivedDeviceVerificationRequest, + this, + [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) { + if (!msg.transaction_id) + return; + + auto txnid = QString::fromStdString(msg.transaction_id.value()); + if (!this->dvList.contains(txnid)) { + if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( + this, msg, QString::fromStdString(sender), txnid)) { + dvList[txnid] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } + }); + connect(dynamic_cast(parent), + &ChatPage::receivedDeviceVerificationStart, + this, + [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) { + if (!msg.transaction_id) + return; + + auto txnid = QString::fromStdString(msg.transaction_id.value()); + if (!this->dvList.contains(txnid)) { + if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( + this, msg, QString::fromStdString(sender), txnid)) { + dvList[txnid] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } + }); connect(parent, &ChatPage::loggedOut, this, [this]() { isInitialSync_ = true; emit initialSyncChanged(true); @@ -428,6 +364,46 @@ TimelineViewManager::openRoomSettings() const MainWindow::instance()->openRoomSettings(timeline_->roomId()); } +void +TimelineViewManager::verifyUser(QString userid) +{ + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); + + for (std::string room_id : joined_rooms) { + if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && + cache::isRoomEncrypted(room_id)) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), + room_members.end(), + (userid).toStdString()) != room_members.end()) { + auto model = models.value(QString::fromStdString(room_id)); + auto flow = DeviceVerificationFlow::InitiateUserVerification( + this, model.data(), userid); + connect(model.data(), + &TimelineModel::updateFlowEventId, + this, + [this, flow](std::string eventId) { + dvList[QString::fromStdString(eventId)] = flow; + }); + emit newDeviceVerificationRequest(flow.data()); + return; + } + } + } + + emit ChatPage::instance()->showNotification( + tr("No share room with this user found. Create an " + "encrypted room with this user and try again.")); +} +void +TimelineViewManager::verifyDevice(QString userid, QString deviceid) +{ + auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); + this->dvList[flow->transactionId()] = flow; + emit newDeviceVerificationRequest(flow.data()); +} + void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector &event_ids) diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 4779d3c..12e4908 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -24,20 +24,6 @@ class ColorImageProvider; class UserSettings; class ChatPage; -class DeviceVerificationList : public QObject -{ - Q_OBJECT -public: - Q_INVOKABLE void add(QString tran_id); - Q_INVOKABLE void remove(QString tran_id); - Q_INVOKABLE bool exist(QString tran_id); -signals: - void updateProfile(QString userId); - -private: - QVector deviceVerificationList; -}; - class TimelineViewManager : public QObject { Q_OBJECT @@ -77,6 +63,9 @@ public: Q_INVOKABLE void openLeaveRoomDialog() const; Q_INVOKABLE void openRoomSettings() const; + void verifyUser(QString userid); + void verifyDevice(QString userid, QString deviceid); + signals: void clearRoomMessageCount(QString roomid); void updateRoomsLastMessage(QString roomid, const DescInfo &info); @@ -84,11 +73,7 @@ signals: void initialSyncChanged(bool isInitialSync); void replyingEventChanged(QString replyingEvent); void replyClosed(); - void newDeviceVerificationRequest(DeviceVerificationFlow *flow, - QString transactionId, - QString userId, - QString deviceId, - bool isRequest = false); + void newDeviceVerificationRequest(DeviceVerificationFlow *flow); void inviteUsers(QStringList users); void showRoomList(); void narrowViewChanged(); @@ -180,7 +165,7 @@ private: QSharedPointer settings; QHash userColors; - DeviceVerificationList *dvList; + QHash> dvList; }; Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept) Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel) diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 2ea3f7b..2a1eecd 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -6,13 +6,18 @@ #include "Utils.h" #include "mtx/responses/crypto.hpp" #include "timeline/TimelineModel.h" +#include "timeline/TimelineViewManager.h" #include // only for debugging -UserProfile::UserProfile(QString roomid, QString userid, TimelineModel *parent) +UserProfile::UserProfile(QString roomid, + QString userid, + TimelineViewManager *manager_, + TimelineModel *parent) : QObject(parent) , roomid_(roomid) , userid_(userid) + , manager(manager_) , model(parent) { fetchDeviceList(this->userid_); @@ -270,44 +275,18 @@ UserProfile::startChat() emit ChatPage::instance()->createRoom(req); } -DeviceVerificationFlow * -UserProfile::createFlow(bool isVerifyUser) +void +UserProfile::verify(QString device) { - if (!isVerifyUser) - return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice)); + if (!device.isEmpty()) + manager->verifyDevice(userid_, device); else { - std::cout << "CHECKING IF IT TO START ROOM_VERIFICATION OR TO_DEVICE VERIFICATION" - << std::endl; - auto joined_rooms = cache::joinedRooms(); - auto room_infos = cache::getRoomInfo(joined_rooms); - - for (std::string room_id : joined_rooms) { - if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && - cache::isRoomEncrypted(room_id)) { - auto room_members = cache::roomMembers(room_id); - if (std::find(room_members.begin(), - room_members.end(), - (this->userid()).toStdString()) != - room_members.end()) { - std::cout - << "FOUND A ENCRYPTED ROOM WITH THIS USER : " << room_id - << std::endl; - if (this->roomid_.toStdString() == room_id) { - auto newflow = new DeviceVerificationFlow( - this, - DeviceVerificationFlow::Type::RoomMsg, - this->model); - return (std::move(newflow)); - } else { - std::cout << "FOUND A ENCRYPTED ROOM BUT CURRENTLY " - "NOT IN THAT ROOM : " - << room_id << std::endl; - } - } - } - } - - std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl; - return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice)); + manager->verifyUser(userid_); } } + +void +UserProfile::unverify(QString device) +{ + cache::markDeviceUnverified(userid_.toStdString(), device.toStdString()); +} diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index de55b6a..1893372 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -21,6 +21,7 @@ Q_ENUM_NS(Status) class DeviceVerificationFlow; class TimelineModel; +class TimelineViewManager; class DeviceInfo { @@ -84,7 +85,10 @@ class UserProfile : public QObject Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT) public: - UserProfile(QString roomid, QString userid, TimelineModel *parent = nullptr); + UserProfile(QString roomid, + QString userid, + TimelineViewManager *manager_, + TimelineModel *parent = nullptr); DeviceInfoModel *deviceList(); @@ -93,7 +97,8 @@ public: QString avatarUrl(); bool getUserStatus(); - Q_INVOKABLE DeviceVerificationFlow *createFlow(bool isVerifyUser); + Q_INVOKABLE void verify(QString device = ""); + Q_INVOKABLE void unverify(QString device = ""); Q_INVOKABLE void fetchDeviceList(const QString &userID); Q_INVOKABLE void banUser(); // Q_INVOKABLE void ignoreUser(); @@ -105,6 +110,7 @@ private: std::optional cross_verified; DeviceInfoModel deviceList_; bool isUserVerified = false; + TimelineViewManager *manager; TimelineModel *model; void callback_fn(const mtx::responses::QueryKeys &res, From 8a4d85f801368137a0b1c17621947b334e280257 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 5 Oct 2020 22:58:07 +0200 Subject: [PATCH 60/70] Show different verification errors --- .../DeviceVerification.qml | 1 - resources/qml/device-verification/Failed.qml | 28 ++++++++++--------- resources/qml/device-verification/Waiting.qml | 4 +-- src/DeviceVerificationFlow.cpp | 5 ++-- src/DeviceVerificationFlow.h | 4 +-- src/timeline/TimelineViewManager.cpp | 12 ++++++++ src/timeline/TimelineViewManager.h | 1 + 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 4e93df0..64f10b3 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -6,7 +6,6 @@ import im.nheko 1.0 ApplicationWindow { property var flow - property var tran_id title: stack.currentItem.title id: dialog diff --git a/resources/qml/device-verification/Failed.qml b/resources/qml/device-verification/Failed.qml index 6b5d57e..fcff789 100644 --- a/resources/qml/device-verification/Failed.qml +++ b/resources/qml/device-verification/Failed.qml @@ -2,23 +2,29 @@ import QtQuick 2.3 import QtQuick.Controls 2.10 import QtQuick.Layouts 1.10 +import im.nheko 1.0 + Pane { - property string title: qsTr("Verification timed out") + property string title: qsTr("Verification failed") ColumnLayout { spacing: 16 Text { + id: content + Layout.maximumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true + wrapMode: Text.Wrap - id: content text: switch (flow.error) { - case VerificationStatus.UnknownMethod: return qsTr("Device verification timed out.") - case VerificationStatus.MismatchedCommitment: return qsTr("Device verification timed out.") - case VerificationStatus.MismatchedSAS: return qsTr("Device verification timed out.") - case VerificationStatus.KeyMismatch: return qsTr("Device verification timed out.") - case VerificationStatus.Timeout: return qsTr("Device verification timed out.") - case VerificationStatus.OutOfOrder: return qsTr("Device verification timed out.") + case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol.") + case DeviceVerificationFlow.MismatchedCommitment: + case DeviceVerificationFlow.MismatchedSAS: + case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!") + case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out.") + case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification.") + case DeviceVerificationFlow.OutOfOrder: return qsTr("Device verification timed out.") + default: return "Unknown verification error."; } color:colors.text verticalAlignment: Text.AlignVCenter @@ -31,11 +37,7 @@ Pane { Layout.alignment: Qt.AlignRight text: qsTr("Close") - onClicked: { - deviceVerificationList.remove(tran_id); - flow.deleteFlow(); - dialog.close() - } + onClicked: dialog.close() } } } diff --git a/resources/qml/device-verification/Waiting.qml b/resources/qml/device-verification/Waiting.qml index f36910e..38abf76 100644 --- a/resources/qml/device-verification/Waiting.qml +++ b/resources/qml/device-verification/Waiting.qml @@ -20,12 +20,12 @@ Pane { case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.") } - color:colors.text + color: colors.text verticalAlignment: Text.AlignVCenter } BusyIndicator { Layout.alignment: Qt.AlignHCenter - palette: color + palette: colors } RowLayout { Button { diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 99fd7be..79f1de8 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -64,7 +64,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } connect(timeout, &QTimer::timeout, this, [this]() { - this->cancelVerification(DeviceVerificationFlow::Error::Timeout); + if (state_ != Success && state_ != Failed) + this->cancelVerification(DeviceVerificationFlow::Error::Timeout); }); connect(ChatPage::instance(), @@ -114,7 +115,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, return; } error_ = User; - emit errorChanged(); + Emit errorChanged(); setState(Failed); }); diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 1fe3919..a1ceaf8 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -58,7 +58,7 @@ class DeviceVerificationFlow : public QObject Q_OBJECT // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") Q_PROPERTY(QString state READ state NOTIFY stateChanged) - Q_PROPERTY(Error error READ error CONSTANT) + Q_PROPERTY(Error error READ error NOTIFY errorChanged) Q_PROPERTY(QString userId READ getUserId CONSTANT) Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT) Q_PROPERTY(bool sender READ getSender CONSTANT) @@ -203,7 +203,7 @@ private: mtx::common::RelatesTo relation; State state_ = PromptStartVerification; - Error error_; + Error error_ = UnknownMethod; bool isMacVerified = false; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 250cd5f..ed72005 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -396,6 +396,18 @@ TimelineViewManager::verifyUser(QString userid) tr("No share room with this user found. Create an " "encrypted room with this user and try again.")); } + +void +TimelineViewManager::removeVerificationFlow(DeviceVerificationFlow *flow) +{ + for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) { + if (it->second == flow) { + dvList.remove(it->first); + return; + } + } +} + void TimelineViewManager::verifyDevice(QString userid, QString deviceid) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 12e4908..a8bd2e0 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -62,6 +62,7 @@ public: Q_INVOKABLE void openMemberListDialog() const; Q_INVOKABLE void openLeaveRoomDialog() const; Q_INVOKABLE void openRoomSettings() const; + Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); void verifyDevice(QString userid, QString deviceid); From 64d5a193f1059cac8225df7b7c3c9eaedfcfa41a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 6 Oct 2020 17:02:41 +0200 Subject: [PATCH 61/70] Fix in room verification --- .../DeviceVerification.qml | 2 + src/DeviceVerificationFlow.cpp | 75 +++++++---- src/DeviceVerificationFlow.h | 7 +- src/timeline/EventStore.cpp | 118 ++++++++---------- src/timeline/EventStore.h | 8 +- 5 files changed, 103 insertions(+), 107 deletions(-) diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 64f10b3..d6185a0 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -7,6 +7,8 @@ import im.nheko 1.0 ApplicationWindow { property var flow + onClosing: TimelineManager.removeVerificationFlow(flow) + title: stack.currentItem.title id: dialog diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 79f1de8..549569f 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -13,6 +13,15 @@ static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes namespace msgs = mtx::events::msg; +static mtx::events::msg::KeyVerificationMac +key_verification_mac(mtx::crypto::SAS *sas, + mtx::identifiers::User sender, + const std::string &senderDevice, + mtx::identifiers::User receiver, + const std::string &receiverDevice, + const std::string &transactionId, + std::map keys); + DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type, TimelineModel *model, @@ -45,11 +54,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, return; } - for (const auto &[algorithm, key] : - res.device_keys.at(deviceId.toStdString()).keys) { - // TODO: Verify Signatures - this->device_keys[algorithm] = key; - } + this->their_keys = res; }); if (model) { @@ -115,7 +120,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, return; } error_ = User; - Emit errorChanged(); + emit errorChanged(); setState(Failed); }); @@ -160,6 +165,8 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, "|" + this->transaction_id; } + nhlog::ui()->info("Info is: '{}'", info); + if (this->sender == false) { this->sendVerificationKey(); } else { @@ -193,28 +200,40 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, if (msg.relates_to.value().event_id != this->relation.event_id) return; } - std::string info = "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() + - this->deviceId.toStdString() + - http::client()->user_id().to_string() + - http::client()->device_id() + this->transaction_id; - std::vector key_list; + std::map key_list; std::string key_string; - for (auto mac : msg.mac) { - key_string += mac.first + ","; - if (device_keys[mac.first] != "") { - if (mac.second == - this->sas->calculate_mac(this->device_keys[mac.first], - info + mac.first)) { - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::KeyMismatch); - return; - } + for (const auto &mac : msg.mac) { + for (const auto &[deviceid, key] : their_keys.device_keys) + if (key.keys.count(mac.first)) + key_list[mac.first] = key.keys.at(mac.first); + + if (their_keys.master_keys.keys.count(mac.first)) + key_list[mac.first] = their_keys.master_keys.keys[mac.first]; + if (their_keys.user_signing_keys.keys.count(mac.first)) + key_list[mac.first] = + their_keys.user_signing_keys.keys[mac.first]; + if (their_keys.self_signing_keys.keys.count(mac.first)) + key_list[mac.first] = + their_keys.self_signing_keys.keys[mac.first]; + } + auto macs = key_verification_mac(sas.get(), + toClient, + this->deviceId.toStdString(), + http::client()->user_id(), + http::client()->device_id(), + this->transaction_id, + key_list); + + for (const auto &[key, mac] : macs.mac) { + if (mac != msg.mac.at(key)) { + this->cancelVerification( + DeviceVerificationFlow::Error::KeyMismatch); + return; } } - key_string = key_string.substr(0, key_string.length() - 1); - if (msg.keys == this->sas->calculate_mac(key_string, info + "KEY_IDS")) { + + if (msg.keys == macs.keys) { this->isMacVerified = true; this->acceptDevice(); } else { @@ -630,9 +649,13 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, QString event_id_) { QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, other_user_, "")); + new DeviceVerificationFlow(parent_, + Type::RoomMsg, + timelineModel_, + other_user_, + QString::fromStdString(msg.from_device))); - flow->event_id = event_id_.toStdString(); + flow->setEventId(event_id_.toStdString()); if (std::find(msg.methods.begin(), msg.methods.end(), diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index a1ceaf8..9ca45a9 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -56,7 +56,6 @@ using sas_ptr = std::unique_ptr; class DeviceVerificationFlow : public QObject { Q_OBJECT - // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") Q_PROPERTY(QString state READ state NOTIFY stateChanged) Q_PROPERTY(Error error READ error NOTIFY errorChanged) Q_PROPERTY(QString userId READ getUserId CONSTANT) @@ -179,11 +178,7 @@ private: //! Completes the verification flow void acceptDevice(); - // for to_device messages std::string transaction_id; - // for room messages - std::optional room_id; - std::optional event_id; bool sender; Type type; @@ -198,7 +193,7 @@ private: nlohmann::json canonical_json; std::vector sasList; - std::map device_keys; + UserKeyCache their_keys; TimelineModel *model_; mtx::common::RelatesTo relation; diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index dc92a37..141d2ec 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -280,8 +280,6 @@ EventStore::handleSync(const mtx::responses::Timeline &events) } } - handle_room_verification(event); - // decrypting and checking some encrypted messages if (auto encrypted = std::get_if>( @@ -292,81 +290,65 @@ EventStore::handleSync(const mtx::responses::Timeline &events) [](auto e) { return (e.sender != utils::localUser().toStdString()); }, *d_event)) { handle_room_verification(*d_event); - } else { - // only the key.verification.ready sent by localuser's other device - // is of significance as it is used for detecting accepted request - if (std::get_if>(d_event)) { - auto msg = std::get_if>(d_event); - ChatPage::instance()->receivedDeviceVerificationReady( - msg->content); - } } + // else { + // // only the key.verification.ready sent by localuser's other + // device + // // is of significance as it is used for detecting accepted request + // if (std::get_if>(d_event)) { + // auto msg = std::get_if>(d_event); + // ChatPage::instance()->receivedDeviceVerificationReady( + // msg->content); + // } + //} } } +} - if (last_verification_request_event.has_value()) { - if (last_verification_request_event.value().origin_server_ts > - last_verification_cancel_event.origin_server_ts) { - emit startDMVerification(last_verification_request_event.value()); - last_verification_request_event = {}; - } - } +namespace { +template +struct overloaded : Ts... +{ + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; } void EventStore::handle_room_verification(mtx::events::collections::TimelineEvents event) { - if (std::get_if>(&event)) { - auto msg = - std::get>(event); - last_verification_request_event = msg; - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - last_verification_cancel_event = msg; - ChatPage::instance()->receivedDeviceVerificationCancel(msg.content); - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - ChatPage::instance()->receivedDeviceVerificationAccept(msg.content); - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - ChatPage::instance()->receivedDeviceVerificationKey(msg.content); - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - ChatPage::instance()->receivedDeviceVerificationMac(msg.content); - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - ChatPage::instance()->receivedDeviceVerificationReady(msg.content); - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - ChatPage::instance()->receivedDeviceVerificationDone(msg.content); - return; - } else if (std::get_if>( - &event)) { - auto msg = - std::get>(event); - ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender); - return; - } + std::visit( + overloaded{ + [this](const mtx::events::RoomEvent &msg) { + emit startDMVerification(msg); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationCancel(msg.content); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationAccept(msg.content); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationKey(msg.content); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationMac(msg.content); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationReady(msg.content); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationDone(msg.content); + }, + [](const mtx::events::RoomEvent &msg) { + ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender); + }, + [](const auto &) {}, + }, + event); } QVariantList diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index 89a5147..954e271 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -99,7 +99,7 @@ signals: void messageSent(std::string txn_id, std::string event_id); void messageFailed(std::string txn_id); void startDMVerification( - mtx::events::RoomEvent &msg); + const mtx::events::RoomEvent &msg); void updateFlowEventId(std::string event_id); public slots: @@ -123,10 +123,4 @@ private: std::string current_txn; int current_txn_error_count = 0; - - // probably not the best way to do - std::optional> - last_verification_request_event; - mtx::events::RoomEvent - last_verification_cancel_event; }; From 7b6fab33731e369a860ab217709190e9457d6d76 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 7 Oct 2020 23:03:14 +0200 Subject: [PATCH 62/70] Calculate verification status from cross-signing sigs and update dynamically --- resources/qml/UserProfile.qml | 3 +- .../DeviceVerification.qml | 4 +- src/Cache.cpp | 178 ++++++++++++++---- src/Cache.h | 8 +- src/CacheCryptoStructs.h | 21 ++- src/Cache_p.h | 11 +- src/timeline/TimelineModel.cpp | 2 +- src/ui/UserProfile.cpp | 145 ++++---------- src/ui/UserProfile.h | 8 +- 9 files changed, 223 insertions(+), 157 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index e7dcc77..562dd4f 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -56,7 +56,7 @@ ApplicationWindow{ Button { id: verifyUserButton - text: "Verify" + text: qsTr("Verify") Layout.alignment: Qt.AlignHCenter enabled: !profile.isUserVerified visible: !profile.isUserVerified @@ -155,7 +155,6 @@ ApplicationWindow{ onClicked: { if(model.verificationStatus == VerificationStatus.VERIFIED){ profile.unverify(model.deviceId) - deviceVerificationList.updateProfile(newFlow.userId); }else{ profile.verify(model.deviceId); } diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index d6185a0..2e8f750 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -1,6 +1,6 @@ -import QtQuick 2.3 +import QtQuick 2.10 import QtQuick.Controls 2.10 -import QtQuick.Window 2.2 +import QtQuick.Window 2.10 import im.nheko 1.0 diff --git a/src/Cache.cpp b/src/Cache.cpp index 63f6e42..d6da03c 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3184,6 +3184,28 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } txn.commit(); + + std::map tmp; + const auto local_user = utils::localUser().toStdString(); + + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + for (auto &[user_id, update] : updates) { + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } + } + } + for (auto &[user_id, update] : updates) { + if (user_id == local_user) { + for (const auto &[user, status] : tmp) + emit verificationStatusChanged(user); + } else { + emit verificationStatusChanged(user_id); + } + } } void @@ -3236,23 +3258,19 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn, void to_json(json &j, const VerificationCache &info) { - j["verified_master_key"] = info.verified_master_key; - j["cross_verified"] = info.cross_verified; - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void from_json(const json &j, VerificationCache &info) { - info.verified_master_key = j.at("verified_master_key"); - info.cross_verified = j.at("cross_verified").get>(); - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } std::optional -Cache::verificationStatus(const std::string &user_id) +Cache::verificationCache(const std::string &user_id) { lmdb::val verifiedVal; @@ -3298,6 +3316,23 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) txn.commit(); } catch (std::exception &) { } + + const auto local_user = utils::localUser().toStdString(); + std::map tmp; + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) + emit verificationStatusChanged(user); + } else { + emit verificationStatusChanged(user_id); + } } void @@ -3325,27 +3360,112 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) txn.commit(); } catch (std::exception &) { } + + const auto local_user = utils::localUser().toStdString(); + std::map tmp; + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) + emit verificationStatusChanged(user); + } else { + emit verificationStatusChanged(user_id); + } } -void -Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key) +VerificationStatus +Cache::verificationStatus(const std::string &user_id) { - lmdb::val val; + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (verification_storage.status.count(user_id)) + return verification_storage.status.at(user_id); - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); + VerificationStatus status; + + if (auto verifCache = verificationCache(user_id)) { + status.verified_devices = verifCache->device_verified; + } + + const auto local_user = utils::localUser().toStdString(); + + if (user_id == local_user) + status.verified_devices.push_back(http::client()->device_id()); + + verification_storage.status[user_id] = status; + + auto verifyAtLeastOneSig = [](const auto &toVerif, + const std::map &keys, + const std::string &keyOwner) { + if (!toVerif.signatures.count(keyOwner)) + return false; + + for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) { + if (!keys.count(key_id)) + continue; + + if (mtx::crypto::ed25519_verify_signature( + keys.at(key_id), json(toVerif), signature)) + return true; + } + return false; + }; try { - VerificationCache verified_state; - auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); - if (res) { - verified_state = json::parse(std::string_view(val.data(), val.size())); + // for local user verify this device_key -> our master_key -> our self_signing_key + // -> our device_keys + // + // for other user verify this device_key -> our master_key -> our user_signing_key + // -> their master_key -> their self_signing_key -> their device_keys + // + // This means verifying the other user adds 2 extra steps,verifying our user_signing + // key and their master key + auto ourKeys = userKeys(local_user); + auto theirKeys = userKeys(user_id); + if (!ourKeys || !theirKeys) + return status; + + if (!mtx::crypto::ed25519_verify_signature( + olm::client()->identity_keys().ed25519, + json(ourKeys->master_keys), + ourKeys->master_keys.signatures.at(local_user) + .at("ed25519:" + http::client()->device_id()))) + return status; + + auto master_keys = ourKeys->master_keys.keys; + + if (user_id != local_user) { + if (!verifyAtLeastOneSig( + ourKeys->user_signing_keys, master_keys, local_user)) + return status; + + if (!verifyAtLeastOneSig( + theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user)) + return status; + + master_keys = theirKeys->master_keys.keys; } - verified_state.verified_master_key = key; - lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); - txn.commit(); + status.user_verified = true; + + if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) + return status; + + for (const auto &[device, device_key] : theirKeys->device_keys) { + if (verifyAtLeastOneSig( + device_key, theirKeys->self_signing_keys.keys, user_id)) + status.verified_devices.push_back(device_key.device_id); + } + + verification_storage.status[user_id] = status; + return status; } catch (std::exception &) { + return status; } } @@ -3551,28 +3671,22 @@ updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &k } // device & user verification cache -std::optional +std::optional verificationStatus(const std::string &user_id) { return instance_->verificationStatus(user_id); } void -markDeviceVerified(const std::string &user_id, const std::string &key) -{ - instance_->markDeviceVerified(user_id, key); -} - -void -markDeviceUnverified(const std::string &user_id, const std::string &key) +markDeviceVerified(const std::string &user_id, const std::string &device) { - instance_->markDeviceUnverified(user_id, key); + instance_->markDeviceVerified(user_id, device); } void -markMasterKeyVerified(const std::string &user_id, const std::string &key) +markDeviceUnverified(const std::string &user_id, const std::string &device) { - instance_->markMasterKeyVerified(user_id, key); + instance_->markDeviceUnverified(user_id, device); } std::vector diff --git a/src/Cache.h b/src/Cache.h index fca8014..cd96708 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -67,14 +67,12 @@ void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); // device & user verification cache -std::optional +std::optional verificationStatus(const std::string &user_id); void -markDeviceVerified(const std::string &user_id, const std::string &key); +markDeviceVerified(const std::string &user_id, const std::string &device); void -markDeviceUnverified(const std::string &user_id, const std::string &key); -void -markMasterKeyVerified(const std::string &user_id, const std::string &key); +markDeviceUnverified(const std::string &user_id, const std::string &device); //! Load saved data for the display names & avatars. void diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 10636ac..935d649 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -66,6 +66,23 @@ struct OlmSessionStorage std::mutex group_inbound_mtx; }; +//! Verification status of a single user +struct VerificationStatus +{ + //! True, if the users master key is verified + bool user_verified = false; + //! List of all devices marked as verified + std::vector verified_devices; +}; + +//! In memory cache of verification status +struct VerificationStorage +{ + //! mapping of user to verification status + std::map status; + std::mutex verification_storage_mtx; +}; + // this will store the keys of the user with whom a encrypted room is shared with struct UserKeyCache { @@ -90,12 +107,8 @@ struct VerificationCache { //! list of verified device_ids with device-verification std::vector device_verified; - //! list of verified device_ids with cross-signing, calculated from master key - std::vector cross_verified; //! list of devices the user blocks std::vector device_blocked; - //! The verified master key. - std::string verified_master_key; }; void diff --git a/src/Cache_p.h b/src/Cache_p.h index b37eae5..b3f4c58 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -67,10 +67,9 @@ public: const std::vector &user_ids); // device & user verification cache - std::optional verificationStatus(const std::string &user_id); - void markDeviceVerified(const std::string &user_id, const std::string &key); - void markDeviceUnverified(const std::string &user_id, const std::string &key); - void markMasterKeyVerified(const std::string &user_id, const std::string &key); + VerificationStatus verificationStatus(const std::string &user_id); + void markDeviceVerified(const std::string &user_id, const std::string &device); + void markDeviceUnverified(const std::string &user_id, const std::string &device); static void removeDisplayName(const QString &room_id, const QString &user_id); static void removeAvatarUrl(const QString &room_id, const QString &user_id); @@ -283,6 +282,7 @@ signals: void removeNotification(const QString &room_id, const QString &event_id); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); + void verificationStatusChanged(const std::string &userid); private: //! Save an invited room. @@ -576,6 +576,8 @@ private: return QString::fromStdString(event.state_key); } + std::optional verificationCache(const std::string &user_id); + void setNextBatchToken(lmdb::txn &txn, const std::string &token); void setNextBatchToken(lmdb::txn &txn, const QString &token); @@ -600,6 +602,7 @@ private: static QHash AvatarUrls; OlmSessionStorage session_storage; + VerificationStorage verification_storage; }; namespace cache { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index e8d381d..359e95b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1031,7 +1031,7 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: try { if (!mtx::crypto::verify_identity_signature( - json(dev.second), device_id, user_id)) { + dev.second, device_id, user_id)) { nhlog::crypto()->warn( "failed to verify identity keys: {}", json(dev.second).dump(2)); diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 2a1eecd..2bb0370 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -1,5 +1,5 @@ #include "UserProfile.h" -#include "Cache.h" +#include "Cache_p.h" #include "ChatPage.h" #include "DeviceVerificationFlow.h" #include "Logging.h" @@ -8,8 +8,6 @@ #include "timeline/TimelineModel.h" #include "timeline/TimelineViewManager.h" -#include // only for debugging - UserProfile::UserProfile(QString roomid, QString userid, TimelineViewManager *manager_, @@ -21,6 +19,31 @@ UserProfile::UserProfile(QString roomid, , model(parent) { fetchDeviceList(this->userid_); + + connect(cache::client(), + &Cache::verificationStatusChanged, + this, + [this](const std::string &user_id) { + if (user_id != this->userid_.toStdString()) + return; + + auto status = cache::verificationStatus(user_id); + if (!status) + return; + this->isUserVerified = status->user_verified; + emit userStatusChanged(); + + for (auto &deviceInfo : deviceList_.deviceList_) { + deviceInfo.verification_status = + std::find(status->verified_devices.begin(), + status->verified_devices.end(), + deviceInfo.device_id.toStdString()) == + status->verified_devices.end() + ? verification::UNVERIFIED + : verification::VERIFIED; + } + deviceList_.reset(deviceList_.deviceList_); + }); } QHash @@ -126,107 +149,27 @@ UserProfile::fetchDeviceList(const QString &userID) } std::vector deviceInfo; - auto devices = other_user_keys.device_keys; - auto device_verified = cache::verificationStatus(other_user_id); - - if (device_verified.has_value()) { - // TODO: properly check cross-signing signatures here - isUserVerified = !device_verified->verified_master_key.empty(); - } - - std::optional lmk, lsk, luk, mk, sk, uk; + auto devices = other_user_keys.device_keys; + auto verificationStatus = + cache::client()->verificationStatus(other_user_id); - lmk = res.master_keys; - luk = res.user_signing_keys; - lsk = res.self_signing_keys; - mk = other_user_keys.master_keys; - uk = other_user_keys.user_signing_keys; - sk = other_user_keys.self_signing_keys; - - // First checking if the user is verified - if (luk.has_value() && mk.has_value()) { - // iterating through the public key of local user_signing keys - for (auto sign_key : luk.value().keys) { - // checking if the signatures are empty as "at" could - // cause exceptions - auto signs = mk->signatures; - if (!signs.empty() && - signs.find(local_user_id) != signs.end()) { - auto sign = signs.at(local_user_id); - try { - isUserVerified = - isUserVerified || - (olm::client()->ed25519_verify_sig( - sign_key.second, - json(mk.value()), - sign.at(sign_key.first))); - } catch (std::out_of_range &) { - isUserVerified = - isUserVerified || false; - } - } - } - } + isUserVerified = verificationStatus.user_verified; + emit userStatusChanged(); for (const auto &d : devices) { auto device = d.second; verification::Status verified = verification::Status::UNVERIFIED; - if (device_verified.has_value()) { - if (std::find(device_verified->cross_verified.begin(), - device_verified->cross_verified.end(), - d.first) != - device_verified->cross_verified.end()) - verified = verification::Status::VERIFIED; - if (std::find(device_verified->device_verified.begin(), - device_verified->device_verified.end(), - d.first) != - device_verified->device_verified.end()) - verified = verification::Status::VERIFIED; - if (std::find(device_verified->device_blocked.begin(), - device_verified->device_blocked.end(), - d.first) != - device_verified->device_blocked.end()) - verified = verification::Status::BLOCKED; - } else if (isUserVerified) { - device_verified = VerificationCache{}; - } - - // won't check for already verified devices - if (verified != verification::Status::VERIFIED && - isUserVerified) { - if ((sk.has_value()) && (!device.signatures.empty())) { - for (auto sign_key : sk.value().keys) { - auto signs = - device.signatures.at(other_user_id); - try { - if (olm::client() - ->ed25519_verify_sig( - sign_key.second, - json(device), - signs.at( - sign_key.first))) { - verified = - verification::Status:: - VERIFIED; - device_verified.value() - .cross_verified - .push_back(d.first); - } - } catch (std::out_of_range &) { - } - } - } - } - - // TODO(Nico): properly show cross-signing - // if (device_verified.has_value()) { - // device_verified.value().is_user_verified = - // isUserVerified; - // cache::setVerifiedCache(user_id, - // device_verified.value()); - //} + if (std::find(verificationStatus.verified_devices.begin(), + verificationStatus.verified_devices.end(), + device.device_id) != + verificationStatus.verified_devices.end() && + mtx::crypto::verify_identity_signature( + device, + DeviceId(device.device_id), + UserId(other_user_id))) + verified = verification::Status::VERIFIED; deviceInfo.push_back( {QString::fromStdString(d.first), @@ -235,14 +178,6 @@ UserProfile::fetchDeviceList(const QString &userID) verified}); } - std::cout << (isUserVerified ? "Yes" : "No") << std::endl; - - std::sort(deviceInfo.begin(), - deviceInfo.end(), - [](const DeviceInfo &a, const DeviceInfo &b) { - return a.device_id > b.device_id; - }); - this->deviceList_.queueReset(std::move(deviceInfo)); }); }); diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 1893372..77b2232 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -74,6 +74,8 @@ public slots: private: std::vector deviceList_; + + friend class UserProfile; }; class UserProfile : public QObject @@ -83,7 +85,7 @@ class UserProfile : public QObject Q_PROPERTY(QString userid READ userid CONSTANT) Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) - Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT) + Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged) public: UserProfile(QString roomid, QString userid, @@ -105,9 +107,11 @@ public: Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); +signals: + void userStatusChanged(); + private: QString roomid_, userid_; - std::optional cross_verified; DeviceInfoModel deviceList_; bool isUserVerified = false; TimelineViewManager *manager; From cd43147b77da5852c336fcac857da5c6e7f09569 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 16:17:38 +0200 Subject: [PATCH 63/70] Implement signature upload for own master key --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/Cache.cpp | 1 + src/DeviceVerificationFlow.cpp | 49 ++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bdfc779..9280f7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -341,7 +341,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG f84611f129b46746a4b586acaba54fc31a303bc6 + GIT_TAG ad5575bc24089dc385e97d9ace026414b618775c ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index da1b5a3..930b39e 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "f84611f129b46746a4b586acaba54fc31a303bc6", + "commit": "ad5575bc24089dc385e97d9ace026414b618775c", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/Cache.cpp b/src/Cache.cpp index d6da03c..d1afa2a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3591,6 +3591,7 @@ init(const QString &user_id) qRegisterMetaType>(); qRegisterMetaType>(); qRegisterMetaType>(); + qRegisterMetaType(); instance_ = std::make_unique(user_id); } diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 549569f..97f4259 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -234,6 +234,55 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } if (msg.keys == macs.keys) { + mtx::requests::KeySignaturesUpload req; + if (utils::localUser().toStdString() == this->toClient.to_string()) { + // self verification, sign master key with device key, if we + // verified it + for (const auto &mac : msg.mac) { + if (their_keys.master_keys.keys.count(mac.first)) { + json j = their_keys.master_keys; + j.erase("signatures"); + j.erase("unsigned"); + mtx::crypto::CrossSigningKeys master_key = j; + master_key + .signatures[utils::localUser().toStdString()] + ["ed25519:" + + http::client()->device_id()] = + olm::client()->sign_message(j.dump()); + req.signatures[utils::localUser().toStdString()] + [master_key.keys.at(mac.first)] = + master_key; + } + } + // TODO(Nico): Sign their device key with self signing key + } else { + // TODO(Nico): Sign their master key with user signing key + } + + if (!req.signatures.empty()) { + http::client()->keys_signatures_upload( + req, + [](const mtx::responses::KeySignaturesUpload &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error( + "failed to upload signatures: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + } + + for (const auto &[user_id, tmp] : res.errors) + for (const auto &[key_id, e] : tmp) + nhlog::net()->error( + "signature error for user {} and key " + "id {}: {}, {}", + user_id, + key_id, + e.errcode, + e.error); + }); + } + this->isMacVerified = true; this->acceptDevice(); } else { From 08d5a84cbdbb8937dbe4d43817e01d8ae00760cc Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 17:26:07 +0200 Subject: [PATCH 64/70] Fix issues with old qt and bump to 5.10 --- .travis.yml | 32 ++++++++++++++-------------- CMakeLists.txt | 4 ++-- README.md | 2 +- src/timeline/TimelineViewManager.cpp | 12 ++++++----- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index eec3229..49c6eb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,21 +63,21 @@ matrix: env: - CXX=g++-8 - CC=gcc-8 - - QT_PKG=59 + - QT_PKG=510 addons: apt: sources: - ubuntu-toolchain-r-test - - sourceline: 'ppa:beineri/opt-qt597-xenial' + - sourceline: 'ppa:beineri/opt-qt-5.10.1-xenial' packages: - g++-8 - ninja-build - - qt59base - - qt59tools - - qt59svg - - qt59multimedia - - qt59quickcontrols2 - - qt59graphicaleffects + - qt510base + - qt510tools + - qt510svg + - qt510multimedia + - qt510quickcontrols2 + - qt510graphicaleffects - liblmdb-dev - libgl1-mesa-dev # needed for missing gl.h - os: linux @@ -85,23 +85,23 @@ matrix: env: - CXX=clang++-6.0 - CC=clang-6.0 - - QT_PKG=59 + - QT_PKG=510 addons: apt: sources: - ubuntu-toolchain-r-test - llvm-toolchain-xenial-6.0 - - sourceline: 'ppa:beineri/opt-qt597-xenial' + - sourceline: 'ppa:beineri/opt-qt-5.10.1-xenial' packages: - clang++-6.0 - g++-7 - ninja-build - - qt59base - - qt59tools - - qt59svg - - qt59multimedia - - qt59quickcontrols2 - - qt59graphicaleffects + - qt510base + - qt510tools + - qt510svg + - qt510multimedia + - qt510quickcontrols2 + - qt510graphicaleffects - liblmdb-dev - libgl1-mesa-dev # needed for missing gl.h - os: linux diff --git a/CMakeLists.txt b/CMakeLists.txt index 4348e81..2f83a86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,9 +142,9 @@ if (APPLE) endif(APPLE) if (Qt5Widgets_FOUND) - if (Qt5Widgets_VERSION VERSION_LESS 5.9.0) + if (Qt5Widgets_VERSION VERSION_LESS 5.10.0) message(STATUS "Qt version ${Qt5Widgets_VERSION}") - message(WARNING "Minimum supported Qt5 version is 5.9!") + message(WARNING "Minimum supported Qt5 version is 5.10!") endif() endif(Qt5Widgets_FOUND) diff --git a/README.md b/README.md index 2d24165..92b5546 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ sudo pacman -S qt5-base \ ##### Gentoo Linux ```bash -sudo emerge -a ">=dev-qt/qtgui-5.9.0" media-libs/fontconfig +sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig ``` ##### Ubuntu 20.04 diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 1815117..b11c496 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -125,11 +125,13 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side"); + + static auto self = this; qmlRegisterSingletonType( - "im.nheko", 1, 0, "TimelineManager", [this](QQmlEngine *, QJSEngine *) { return this; }); + "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) { return self; }); qmlRegisterSingletonType( - "im.nheko", 1, 0, "Settings", [this](QQmlEngine *, QJSEngine *) { - return this->settings.data(); + "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) { + return self->settings.data(); }); qRegisterMetaType(); @@ -413,8 +415,8 @@ void TimelineViewManager::removeVerificationFlow(DeviceVerificationFlow *flow) { for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) { - if (it->second == flow) { - dvList.remove(it->first); + if ((*it).second == flow) { + dvList.remove((*it).first); return; } } From e5fb9a25ea06f03129961a0df507361baae4b67a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 18:16:30 +0200 Subject: [PATCH 65/70] Try to please the CI gods --- src/DeviceVerificationFlow.h | 2 +- src/timeline/TimelineViewManager.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 9ca45a9..d70374d 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -211,7 +211,7 @@ private: body[this->toClient][deviceId.toStdString()] = msg; http::client()->send_to_device( - this->transaction_id, body, [this](mtx::http::RequestErr err) { + this->transaction_id, body, [](mtx::http::RequestErr err) { if (err) nhlog::net()->warn( "failed to send verification to_device message: {} {}", diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b11c496..7c81ca8 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -128,9 +128,11 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin static auto self = this; qmlRegisterSingletonType( - "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) { return self; }); + "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * { + return self; + }); qmlRegisterSingletonType( - "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) { + "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { return self->settings.data(); }); From 8ec76daedac7419f6ba5eb1f2edf00a6d551f752 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 18:30:31 +0200 Subject: [PATCH 66/70] Send master key in verification flow (if we trust it) --- src/DeviceVerificationFlow.cpp | 23 +++++++++++++++++++++++ src/DeviceVerificationFlow.h | 3 +++ 2 files changed, 26 insertions(+) diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 97f4259..4350a90 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -57,6 +57,25 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, this->their_keys = res; }); + ChatPage::instance()->query_keys( + http::client()->user_id().to_string(), + [this](const UserKeyCache &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + + if (res.master_keys.keys.empty()) + return; + + if (auto status = + cache::verificationStatus(http::client()->user_id().to_string()); + status && status->user_verified) + this->our_trusted_master_key = res.master_keys.keys.begin()->second; + }); + if (model) { connect(this->model_, &TimelineModel::updateFlowEventId, @@ -655,6 +674,10 @@ DeviceVerificationFlow::sendVerificationMac() std::map key_list; key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519; + // send our master key, if we trust it + if (!this->our_trusted_master_key.empty()) + key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key; + mtx::events::msg::KeyVerificationMac req = key_verification_mac(sas.get(), http::client()->user_id(), diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index d70374d..70b5d9b 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -185,6 +185,9 @@ private: mtx::identifiers::User toClient; QString deviceId; + // public part of our master key, when trusted or empty + std::string our_trusted_master_key; + mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji; QTimer *timeout = nullptr; sas_ptr sas; From 56ba7de50135f566766da334b6ae955f7d6344c6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 18:38:55 +0200 Subject: [PATCH 67/70] Fix unused variable warnings on old compilers --- src/Cache.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index d1afa2a..08b6f15 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3191,6 +3191,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query { std::unique_lock lock(verification_storage.verification_storage_mtx); for (auto &[user_id, update] : updates) { + (void)update; if (user_id == local_user) { std::swap(tmp, verification_storage.status); } else { @@ -3199,9 +3200,12 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } } for (auto &[user_id, update] : updates) { + (void)update; if (user_id == local_user) { - for (const auto &[user, status] : tmp) + for (const auto &[user, status] : tmp) { + (void)status; emit verificationStatusChanged(user); + } } else { emit verificationStatusChanged(user_id); } @@ -3328,8 +3332,10 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) } } if (user_id == local_user) { - for (const auto &[user, status] : tmp) + for (const auto &[user, status] : tmp) { + (void)status; emit verificationStatusChanged(user); + } } else { emit verificationStatusChanged(user_id); } @@ -3372,8 +3378,10 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) } } if (user_id == local_user) { - for (const auto &[user, status] : tmp) + for (const auto &[user, status] : tmp) { + (void)status; emit verificationStatusChanged(user); + } } else { emit verificationStatusChanged(user_id); } @@ -3457,6 +3465,7 @@ Cache::verificationStatus(const std::string &user_id) return status; for (const auto &[device, device_key] : theirKeys->device_keys) { + (void)device; if (verifyAtLeastOneSig( device_key, theirKeys->self_signing_keys.keys, user_id)) status.verified_devices.push_back(device_key.device_id); From 684cfacfadd7d29556f32a3e566d2b1ea76fed1c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 19:12:14 +0200 Subject: [PATCH 68/70] Bump spdlog version --- io.github.NhekoReborn.Nheko.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 930b39e..59f4fa4 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -73,9 +73,9 @@ ], "sources": [ { - "sha256": "3dbcbfd8c07e25f5e0d662b194d3a7772ef214358c49ada23c044c4747ce8b19", + "sha256": "5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb", "type": "archive", - "url": "https://github.com/gabime/spdlog/archive/v1.1.0.tar.gz" + "url": "https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz" } ] }, From 57a6c05eab5a8c712fd480213152c7c26f096b83 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 19:29:42 +0200 Subject: [PATCH 69/70] More unused variables in bindings --- src/DeviceVerificationFlow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 4350a90..aa1a960 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -223,9 +223,11 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, std::map key_list; std::string key_string; for (const auto &mac : msg.mac) { - for (const auto &[deviceid, key] : their_keys.device_keys) + for (const auto &[deviceid, key] : their_keys.device_keys) { + (void)deviceid; if (key.keys.count(mac.first)) key_list[mac.first] = key.keys.at(mac.first); + } if (their_keys.master_keys.keys.count(mac.first)) key_list[mac.first] = their_keys.master_keys.keys[mac.first]; From 392d7d5568a2d122061597e5373e63d2469c1a3d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 20:07:43 +0200 Subject: [PATCH 70/70] Try to fix windows build --- src/timeline/EventStore.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 141d2ec..d3c5c3f 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -112,9 +112,9 @@ EventStore::EventStore(std::string room_id, QObject *) } emit messageSent(txn_id, event_id.event_id.to_string()); - if constexpr (mtx::events::message_content_to_type< - decltype(e.content)> == - mtx::events::EventType::RoomEncrypted) { + if constexpr (std::is_same_v< + decltype(e.content), + mtx::events::msg::Encrypted>) { auto event = decryptEvent({room_id_, e.event_id}, e); if (auto dec =