Add UI for entering key backup passphrase

This commit is contained in:
Tobias Fella
2023-07-29 23:18:49 +02:00
parent a0bfd34951
commit e07b876677
8 changed files with 188 additions and 0 deletions

View File

@@ -309,6 +309,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/IgnoredUsersDialog.qml
qml/AccountData.qml
qml/StateKeys.qml
qml/UnlockSSSSDialog.qml
RESOURCES
qml/confetti.png
qml/glowdot.png

View File

@@ -377,6 +377,14 @@ AccountRegistry &Controller::accounts()
return m_accountRegistry;
}
QString Controller::loadFileContent(const QString &path) const
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QFile::ReadOnly);
return QString::fromLatin1(file.readAll());
}
#include "moc_controller.cpp"
void Controller::setTestMode(bool test)
@@ -393,3 +401,12 @@ void Controller::removeConnection(const QString &userId)
SettingsGroup("Accounts"_ls).remove(userId);
}
}
bool Controller::ssssSupported() const
{
#if __has_include("Quotient/e2ee/sssshandler.h")
return true;
#else
return false;
#endif
}

View File

@@ -56,6 +56,8 @@ class Controller : public QObject
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
Q_PROPERTY(bool ssssSupported READ ssssSupported CONSTANT)
public:
static Controller &instance();
static Controller *create(QQmlEngine *engine, QJSEngine *)
@@ -92,12 +94,16 @@ public:
*/
static void listenForNotifications();
Q_INVOKABLE QString loadFileContent(const QString &path) const;
Quotient::AccountRegistry &accounts();
static void setTestMode(bool testMode);
Q_INVOKABLE void removeConnection(const QString &userId);
bool ssssSupported() const;
private:
explicit Controller(QObject *parent = nullptr);

View File

@@ -35,6 +35,11 @@
#include "neochat-version.h"
#include <Quotient/accountregistry.h>
#if __has_include("Quotient/e2ee/sssshandler.h")
#include <Quotient/e2ee/sssshandler.h>
#endif
#include <Quotient/keyverificationsession.h>
#include <Quotient/networkaccessmanager.h>
#include "blurhashimageprovider.h"
@@ -230,6 +235,9 @@ int main(int argc, char *argv[])
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
qmlRegisterUncreatableType<KeyVerificationSession>("com.github.quotient_im.libquotient", 1, 0, "KeyVerificationSession", {});
#if __has_include("Quotient/e2ee/sssshandler.h")
qmlRegisterType<SSSSHandler>("com.github.quotient_im.libquotient", 1, 0, "SSSSHandler");
#endif
QQmlApplicationEngine engine;

View File

@@ -161,6 +161,10 @@
<label>Enable threads</label>
<default>false</default>
</entry>
<entry name="SecretBackup" type="bool">
<label>Enable secret backup</label>
<default>false</default>
</entry>
</group>
</kcfg>

View File

@@ -62,6 +62,15 @@ QQC2.Menu {
height: Kirigami.Units.gridUnit * 42
})
}
QQC2.MenuItem {
text: i18nc("@action:inmenu", "Open Secret Backup")
icon.name: "unlock"
visible: Config.secretBackup
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog.qml'), {}, {
title: i18nc("@title:window", "Open Key Backup")
})
enabled: Controller.ssssSupported
}
QQC2.MenuItem {
text: i18n("Logout")
icon.name: "list-remove-user"

View File

@@ -23,5 +23,11 @@ FormCard.FormCardPage {
onToggled: Config.threads = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup")
checked: Config.secretBackup
onToggled: Config.secretBackup = checked
}
}
}

View File

@@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import com.github.quotient_im.libquotient
import org.kde.neochat
FormCard.FormCardPage {
id: root
title: i18nc("@title:window", "Load your encrypted messages")
topPadding: Kirigami.Units.gridUnit
leftPadding: 0
rightPadding: 0
header: KirigamiComponents.Banner {
id: banner
showCloseButton: true
visible: false
type: Kirigami.MessageType.Error
}
property SSSSHandler ssssHandler: SSSSHandler {
id: ssssHandler
property bool processing: false
connection: Controller.activeConnection
onKeyBackupUnlocked: {
ssssHandler.processing = false
root.closeDialog()
}
onError: error => {
if (error !== SSSSHandler.WrongKeyError) {
banner.text = error
banner.visible = true
return;
}
passwordField.clear()
ssssHandler.processing = false
banner.text = i18nc("@info:status", "The security phrase was not correct.")
banner.visible = true
}
}
FormCard.FormHeader {
title: i18nc("@title", "Unlock using Passphrase")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
description: i18nc("@info", "If you have a backup passphrase for this account, enter it below.")
}
FormCard.FormTextFieldDelegate {
id: passwordField
label: i18nc("@label:textbox", "Backup Password:")
echoMode: TextInput.Password
}
FormCard.FormButtonDelegate {
id: unlockButton
text: i18nc("@action:button", "Unlock")
icon.name: "unlock"
enabled: passwordField.text.length > 0 && !ssssHandler.processing
onClicked: {
ssssHandler.processing = true
banner.visible = false
ssssHandler.unlockSSSSWithPassphrase(passwordField.text)
}
}
}
FormCard.FormHeader {
title: i18nc("@title", "Unlock using Security Key")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
description: i18nc("@info", "If you have a security key for this account, enter it below or upload it as a file.")
}
FormCard.FormTextFieldDelegate {
id: securityKeyField
label: i18nc("@label:textbox", "Security Key:")
echoMode: TextInput.Password
}
FormCard.FormButtonDelegate {
id: uploadSecurityKeyButton
text: i18nc("@action:button", "Upload from File")
icon.name: "cloud-upload"
enabled: !ssssHandler.processing
onClicked: {
ssssHandler.processing = true
openFileDialog.open()
}
}
FormCard.FormButtonDelegate {
id: unlockSecurityKeyButton
text: i18nc("@action:button", "Unlock")
icon.name: "unlock"
enabled: securityKeyField.text.length > 0 && !ssssHandler.processing
onClicked: {
ssssHandler.processing = true
ssssHandler.unlockSSSSFromSecurityKey(securityKeyField.text)
}
}
}
FormCard.FormHeader {
title: i18nc("@title", "Unlock from Cross-Signing")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
description: i18nc("@info", "If you have previously verified this device, you can try loading the backup key from other devices by clicking the button below.")
}
FormCard.FormButtonDelegate {
id: unlockCrossSigningButton
icon.name: "emblem-shared-symbolic"
text: i18nc("@action:button", "Request from other Devices")
enabled: !ssssHandler.processing
onClicked: {
ssssHandler.processing = true
ssssHandler.unlockSSSSFromCrossSigning()
}
}
}
property OpenFileDialog openFileDialog: OpenFileDialog {
id: openFileDialog
onChosen: securityKeyField.text = Controller.loadFileContent(path)
}
}