diff --git a/TODO.md b/TODO.md index f4710c6d..034b9a6b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # TODO +- handle `{}` bad `DevicesResponse` - verify & delete devices - sessions page size - flickshortcuts diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index bcb0e522..0d73eb89 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -1325,6 +1325,13 @@ class MatrixClient(nio.AsyncClient): return False + async def verify_device_id(self, user_id: str, device_id: str) -> None: + """Mark a device as verified.""" + + self.verify_device(self.olm.device_store[user_id][device_id]) + + + # Functions to register/modify data into models async def update_account_unread_counts(self) -> None: diff --git a/src/gui/Pages/AccountSettings/DeviceDelegate.qml b/src/gui/Pages/AccountSettings/DeviceDelegate.qml index 915cabad..df1b5d19 100644 --- a/src/gui/Pages/AccountSettings/DeviceDelegate.qml +++ b/src/gui/Pages/AccountSettings/DeviceDelegate.qml @@ -7,12 +7,14 @@ import "../../Base/ButtonLayout" import "../../Base/HTile" HTile { - id: device + id: deviceTile property HListView view + property string userId - signal renameDeviceRequest(string name) + signal verified() + signal renameRequest(string name) backgroundColor: "transparent" @@ -22,7 +24,7 @@ HTile { rightPadding: 0 contentItem: ContentRow { - tile: device + tile: deviceTile spacing: 0 HCheckBox { @@ -42,13 +44,13 @@ HTile { } TitleRightInfoLabel { - tile: device + tile: deviceTile text: utils.smartFormatDate(model.last_seen_date) } } SubtitleLabel { - tile: device + tile: deviceTile font.family: theme.fontFamily.mono text: model.last_seen_ip ? @@ -86,7 +88,7 @@ HTile { defaultText: model.display_name maximumLength: 255 horizontalAlignment: Qt.AlignHCenter - onAccepted: renameDeviceRequest(text) + onAccepted: renameRequest(text) Layout.fillWidth: true } @@ -94,7 +96,7 @@ HTile { HButton { icon.name: "apply" icon.color: theme.colors.positiveBackground - onClicked: renameDeviceRequest(nameField.text) + onClicked: renameRequest(nameField.text) Layout.fillHeight: true } @@ -133,12 +135,31 @@ HTile { width: parent.width ApplyButton { - enabled: [ - "unset", "ignored", "blacklisted", - ].includes(model.type) - - text: qsTr("Verify") + enabled: model.type !== "no_keys" icon.name: "device-verify" + text: + model.type === "current" ? qsTr("Get verified") : + model.type === "verified" ? qsTr("Reverify") : + qsTr("Verify") + + onClicked: { + actionMenu.focusOnClosed = null + + utils.makePopup( + "Popups/KeyVerificationPopup.qml", + view, + { + focusOnClosed: nameField, + userId: deviceTile.userId, + deviceOwner: deviceTile.userId, + deviceId: model.id, + deviceName: model.display_name, + ed25519Key: model.ed25519_key, + deviceIsCurrent: model.type === "current", + verifiedCallback: deviceTile.verified, + }, + ) + } } CancelButton { diff --git a/src/gui/Pages/AccountSettings/Sessions.qml b/src/gui/Pages/AccountSettings/Sessions.qml index eafaf12d..43f623da 100644 --- a/src/gui/Pages/AccountSettings/Sessions.qml +++ b/src/gui/Pages/AccountSettings/Sessions.qml @@ -69,6 +69,7 @@ HColumnPage { section in counts ? counts[section] += 1 : counts[section] = 1 } + print( "rec") return counts } @@ -77,7 +78,9 @@ HColumnPage { delegate: DeviceDelegate { width: deviceList.width view: deviceList - onRenameDeviceRequest: name => renameDevice(model.index, name) + userId: page.userId + onVerified: page.loadDevices() + onRenameRequest: name => renameDevice(model.index, name) } section.property: "type" diff --git a/src/gui/Popups/KeyVerificationPopup.qml b/src/gui/Popups/KeyVerificationPopup.qml new file mode 100644 index 00000000..1812b00a --- /dev/null +++ b/src/gui/Popups/KeyVerificationPopup.qml @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import "../Base" +import "../Base/ButtonLayout" + +HFlickableColumnPopup { + id: popup + + + property string userId + property string deviceOwner + property string deviceId + property string deviceName + property string ed25519Key + property bool deviceIsCurrent: false + property var verifiedCallback: null + + + page.footer: ButtonLayout { + ApplyButton { + visible: ! deviceIsCurrent + text: qsTr("They match") + icon.name: "device-verified" + onClicked: { + loading = true + + py.callClientCoro( + userId, + "verify_device_id", + [deviceOwner, deviceId], + () => { + if (verifiedCallback) verifiedCallback() + popup.close() + } + ) + } + } + + CancelButton { + visible: ! popup.deviceIsCurrent + text: qsTr("They differ") + icon.name: "device-blacklisted" + onClicked: { + // XXX + popup.close() + } + } + + CancelButton { + id: cancelButton + onClicked: popup.close() + + Binding on text { + value: qsTr("Exit") + when: popup.deviceIsCurrent + } + } + } + + SummaryLabel { + text: qsTr("Do these info match on your other session?") + } + + HSelectableLabel { + function formatInfo(info, value) { + return ( + `
  • ` + + info + + `` + + value + + `

  • ` + ) + } + + wrapMode: Text.Wrap + textFormat: Qt.RichText + text: ( + "" + ) + + Layout.fillWidth: true + } + + DetailsLabel { + text: + deviceIsCurrent ? + qsTr( + "Compare with the info in the account settings of the " + + "session that wants to verify this one, and " + + "indicate to that other session whether they match. " + + "If they differ, your account's security may be compromised." + ) : + qsTr( + "Compare with the info in your other session's account " + + "settings. " + + "If they differ, your account's security may be compromised." + ) + } + + onOpened: cancelButton.forceActiveFocus() +}