diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 85087bc40cb6f4d4ab9a714b68ac69116c6a5f05..72ac49e1fb46dec1ee22795301109f58fd2d426b 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -502,6 +502,86 @@ Page { Layout.fillWidth: true } + Rectangle { + id: unverifiedStuffBubble + color: Qt.lighter(Nheko.theme.orange, verifyButtonHovered.hovered ? 1.2 : 1.0) + Layout.fillWidth: true + implicitHeight: explanation.height + Nheko.paddingMedium * 2 + visible: SelfVerificationStatus.status != SelfVerificationStatus.AllVerified + + RowLayout { + id: unverifiedStuffBubbleContainer + width: parent.width + height: explanation.height + Nheko.paddingMedium * 2 + spacing: 0 + + Label { + id: explanation + Layout.margins: Nheko.paddingMedium + Layout.rightMargin: Nheko.paddingSmall + color: Nheko.colors.buttonText + Layout.fillWidth: true + text: switch(SelfVerificationStatus.status) { + case SelfVerificationStatus.NoMasterKey: + //: Cross-signing setup has not run yet. + return qsTr("Encryption not set up"); + case SelfVerificationStatus.UnverifiedMasterKey: + //: The user just signed in with this device and hasn't verified their master key. + return qsTr("Unverified login"); + case SelfVerificationStatus.UnverifiedDevices: + //: There are unverified devices signed in to this account. + return qsTr("Please verify your other devices"); + default: + return "" + } + textFormat: Text.PlainText + wrapMode: Text.Wrap + } + + ImageButton { + id: closeUnverifiedBubble + + Layout.rightMargin: Nheko.paddingMedium + Layout.topMargin: Nheko.paddingMedium + Layout.alignment: Qt.AlignRight | Qt.AlignTop + hoverEnabled: true + width: fontMetrics.font.pixelSize + height: fontMetrics.font.pixelSize + image: ":/icons/icons/ui/remove-symbol.png" + ToolTip.visible: closeUnverifiedBubble.hovered + ToolTip.text: qsTr("Close") + onClicked: unverifiedStuffBubble.visible = false + } + + } + + HoverHandler { + id: verifyButtonHovered + enabled: !closeUnverifiedBubble.hovered + + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + } + + TapHandler { + enabled: !closeUnverifiedBubble.hovered + acceptedButtons: Qt.LeftButton + onSingleTapped: { + if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedDevices) { + SelfVerificationStatus.verifyUnverifiedDevices(); + } else { + SelfVerificationStatus.statusChanged(); + } + } + } + } + + Rectangle { + color: Nheko.theme.separator + height: 1 + Layout.fillWidth: true + visible: unverifiedStuffBubble.visible + } + } footer: ColumnLayout { diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index 26af82b3c555541d54d37013c75c23cfe4fa00d1..a7502d8df9148887c74ee0c52315c71c086525b7 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -277,6 +277,10 @@ Item { bootstrapCrosssigning.open(); else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) verifyMasterKey.open(); + else { + bootstrapCrosssigning.close(); + verifyMasterKey.close(); + } } diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index 5ae2d25b25c6e5e835147af5a5825e6e69410f1b..7e521605fed21df5ce458ea3f0b5392145278b41 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -23,7 +23,10 @@ Pane { text: { if (flow.sender) { if (flow.isSelfVerification) - return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); + if (flow.isMultiDeviceVerification) + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.)"); + else + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); else return qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party."); } else { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5b77a9d4b47fd0eaba0b0d403764e242d431e576..45cc642dbb95d57c88a5112f9f8d868e168ef35e 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -208,8 +208,7 @@ Cache::Cache(const QString &userId, QObject *parent) [this](const std::string &u) { if (u == localUserId_.toStdString()) { auto status = verificationStatus(u); - if (status.unverified_device_count || !status.user_verified) - emit selfUnverified(); + emit selfVerificationStatusChanged(); } }, Qt::QueuedConnection); @@ -4265,6 +4264,7 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { std::swap(tmp, verification_storage.status); + verification_storage.status.clear(); } else { verification_storage.status.erase(user_id); } @@ -4274,9 +4274,8 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } void @@ -4316,9 +4315,8 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } VerificationStatus diff --git a/src/Cache_p.h b/src/Cache_p.h index f7db77d4403e1e2f113a71728d6e48a2cefaf7e8..651d73d7f358b53c52ad8712b4b46f54a51f4910 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -310,7 +310,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); - void selfUnverified(); + void selfVerificationStatusChanged(); void secretChanged(const std::string name); private: diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h index 55713def52cf194ee37b3dbde6bd8b5931bb85f4..537adf31bd8371f05854abb1276585e93b202c8f 100644 --- a/src/encryption/DeviceVerificationFlow.h +++ b/src/encryption/DeviceVerificationFlow.h @@ -69,6 +69,7 @@ class DeviceVerificationFlow : public QObject Q_PROPERTY(std::vector<int> sasList READ getSasList CONSTANT) Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT) Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT) + Q_PROPERTY(bool isMultiDeviceVerification READ isMultiDeviceVerification CONSTANT) public: enum State @@ -139,6 +140,7 @@ public: return this->type == DeviceVerificationFlow::Type::ToDevice; } bool isSelfVerification() const; + bool isMultiDeviceVerification() const { return deviceIds.size() > 1; } void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index d4be444274dc4141985e5eeee7c0fb7a892d5c9a..ebb6b548843946d731589537c07f10a3e3339297 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -20,7 +20,7 @@ SelfVerificationStatus::SelfVerificationStatus(QObject *o) { connect(MainWindow::instance(), &MainWindow::reload, this, [this] { connect(cache::client(), - &Cache::selfUnverified, + &Cache::selfVerificationStatusChanged, this, &SelfVerificationStatus::invalidate, Qt::UniqueConnection); @@ -233,6 +233,24 @@ void SelfVerificationStatus::verifyUnverifiedDevices() { nhlog::db()->info("Clicked verify unverified devices"); + const auto this_user = http::client()->user_id().to_string(); + + auto keys = cache::client()->userKeys(this_user); + auto verif = cache::client()->verificationStatus(this_user); + + if (!keys) + return; + + std::vector<QString> devices; + for (const auto &[device, keys] : keys->device_keys) { + (void)keys; + if (!verif.verified_devices.count(device)) + devices.push_back(QString::fromStdString(device)); + } + + if (!devices.empty()) + ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices( + QString::fromStdString(this_user), std::move(devices)); } void diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index d6f0b72f8dbba76acc36b25fe257c8f9c90d801a..d7c92fb88d0f1091a63ec72009075593543f80b6 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -62,13 +62,16 @@ Theme::Theme(std::string_view theme) sidebarBackground_ = QColor("#233649"); alternateButton_ = QColor("#ccc"); red_ = QColor("#a82353"); + orange_ = QColor("#fcbe05"); } else if (theme == "dark") { sidebarBackground_ = QColor("#2d3139"); alternateButton_ = QColor("#414A59"); red_ = QColor("#a82353"); + orange_ = QColor("#fcc53a"); } else { sidebarBackground_ = p.window().color(); alternateButton_ = p.dark().color(); red_ = QColor("red"); + orange_ = QColor("orange"); } } diff --git a/src/ui/Theme.h b/src/ui/Theme.h index f3e7c287d174e56821da684b4eac283a80a7b4ec..4fef897d3394aa68d52ad2a7ac02e804a2b570c8 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -62,6 +62,7 @@ class Theme : public QPalette Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT) Q_PROPERTY(QColor separator READ separator CONSTANT) Q_PROPERTY(QColor red READ red CONSTANT) + Q_PROPERTY(QColor orange READ orange CONSTANT) public: Theme() {} explicit Theme(std::string_view theme); @@ -71,7 +72,8 @@ public: QColor alternateButton() const { return alternateButton_; } QColor separator() const { return separator_; } QColor red() const { return red_; } + QColor orange() const { return orange_; } private: - QColor sidebarBackground_, separator_, red_, alternateButton_; + QColor sidebarBackground_, separator_, red_, orange_, alternateButton_; };