From ad5d60f915e8f767363713e7fd1cd57a50e4102b Mon Sep 17 00:00:00 2001 From: MrMirDan Date: Fri, 12 Dec 2025 16:32:16 +0200 Subject: [PATCH] update: ui to set/change password and hint --- client/images/controls/eye-new.svg | 4 + client/images/controls/eye-off-new.svg | 6 + client/images/controls/lock-locked.svg | 3 + client/images/controls/lock-unlocked.svg | 3 + client/resources.qrc | 11 +- client/secure_qsettings.cpp | 20 -- client/secure_qsettings.h | 3 - client/settings.h | 8 +- client/ui/controllers/pageController.h | 1 + client/ui/controllers/settingsController.h | 1 + .../ui/qml/Components/EncryptionIndicator.qml | 66 ++++++ client/ui/qml/Components/PasswordDrawer.qml | 89 +++++++++ .../qml/Pages2/PageSettingsAppEncryption.qml | 148 ++++++++++++++ .../ui/qml/Pages2/PageSettingsAppPassword.qml | 189 ++++++++++++++++++ .../Pages2/PageSettingsAppPasswordConfirm.qml | 148 ++++++++++++++ .../ui/qml/Pages2/PageSettingsApplication.qml | 17 ++ 16 files changed, 689 insertions(+), 28 deletions(-) create mode 100644 client/images/controls/eye-new.svg create mode 100644 client/images/controls/eye-off-new.svg create mode 100644 client/images/controls/lock-locked.svg create mode 100644 client/images/controls/lock-unlocked.svg create mode 100644 client/ui/qml/Components/EncryptionIndicator.qml create mode 100644 client/ui/qml/Components/PasswordDrawer.qml create mode 100644 client/ui/qml/Pages2/PageSettingsAppEncryption.qml create mode 100644 client/ui/qml/Pages2/PageSettingsAppPassword.qml create mode 100644 client/ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml diff --git a/client/images/controls/eye-new.svg b/client/images/controls/eye-new.svg new file mode 100644 index 000000000..84811e489 --- /dev/null +++ b/client/images/controls/eye-new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/eye-off-new.svg b/client/images/controls/eye-off-new.svg new file mode 100644 index 000000000..d9310af7e --- /dev/null +++ b/client/images/controls/eye-off-new.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/lock-locked.svg b/client/images/controls/lock-locked.svg new file mode 100644 index 000000000..99d25f7a7 --- /dev/null +++ b/client/images/controls/lock-locked.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/lock-unlocked.svg b/client/images/controls/lock-unlocked.svg new file mode 100644 index 000000000..739624cd3 --- /dev/null +++ b/client/images/controls/lock-unlocked.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index a293d4c69..d5e6fc3bb 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -19,6 +19,8 @@ images/controls/delete.svg images/controls/download.svg images/controls/edit-3.svg + images/controls/eye-off-new.svg + images/controls/eye-new.svg images/controls/eye-off.svg images/controls/eye.svg images/controls/external-link.svg @@ -32,6 +34,8 @@ images/controls/history.svg images/controls/home.svg images/controls/info.svg + images/controls/lock-locked.svg + images/controls/lock-unlocked.svg images/controls/mail.svg images/controls/map-pin.svg images/controls/more-vertical.svg @@ -127,11 +131,13 @@ ui/qml/Components/HomeContainersListView.qml ui/qml/Components/HomeSplitTunnelingDrawer.qml ui/qml/Components/InstalledAppsDrawer.qml + ui/qml/Components/PasswordDrawer.qml ui/qml/Components/QuestionDrawer.qml ui/qml/Components/SelectLanguageDrawer.qml ui/qml/Components/ServersListView.qml ui/qml/Components/SettingsContainersListView.qml - + ui/qml/Components/EncryptionIndicator.qml + ui/qml/Components/TransportProtoSelector.qml ui/qml/Components/AddSitePanel.qml ui/qml/Config/GlobalConfig.qml @@ -203,6 +209,9 @@ ui/qml/Pages2/PageSettingsApiServerInfo.qml ui/qml/Pages2/PageSettingsApplication.qml ui/qml/Pages2/PageSettingsAppSplitTunneling.qml + ui/qml/Pages2/PageSettingsAppEncryption.qml + ui/qml/Pages2/PageSettingsAppPassword.qml + ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml ui/qml/Pages2/PageSettingsBackup.qml ui/qml/Pages2/PageSettingsConnection.qml ui/qml/Pages2/PageSettingsDns.qml diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index 918964a62..b5f838f3c 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -305,26 +305,6 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) } } -void SecureQSettings::setPassword(const QString &pwd) -{ - m_password = pwd; -} - -void SecureQSettings::setHint(const QString &hint) -{ - m_hint = hint; -} - -QString SecureQSettings::getPassword() const -{ - return m_password; -} - -QString SecureQSettings::getHint() const -{ - return m_hint; -} - static QString opensslErrString() { unsigned long e = ERR_get_error(); diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 017c4447e..ca59b959a 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -57,9 +57,6 @@ private: "Conf/", "Servers/", }; - mutable QString m_password; - mutable QString m_hint; - mutable QByteArray m_key; mutable QByteArray m_iv; diff --git a/client/settings.h b/client/settings.h index fddf1183b..ba21ff541 100644 --- a/client/settings.h +++ b/client/settings.h @@ -105,20 +105,20 @@ public: QString getPassword() const { - return m_settings.getPassword(); + return value("Sec/password", "").toString(); } void setPassword(const QString &pwd) { - m_settings.setPassword(pwd); + setValue("Sec/password", pwd); } QString getHint() const { - return m_settings.getHint(); + return value("Sec/hint", "").toString(); } void setHint(const QString &hint) { - m_settings.setHint(hint); + setValue("Sec/hint", hint); } bool isSaveLogs() const diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 83948f500..c3e4f8dc4 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -34,6 +34,7 @@ namespace PageLoader PageSettingsSplitTunneling, PageSettingsAppSplitTunneling, PageSettingsKillSwitch, + PageSettingsAppEncryption, PageSettingsAppPassword, PageSettingsAppPasswordConfirm, PageSettingsApiServerInfo, diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 2452cea7c..62ea98a63 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -137,6 +137,7 @@ signals: void startMinimizedChanged(); void fileEncryptionStateChanged(); + void changingPassword(); private: QSharedPointer m_serversModel; diff --git a/client/ui/qml/Components/EncryptionIndicator.qml b/client/ui/qml/Components/EncryptionIndicator.qml new file mode 100644 index 000000000..ca4c29a72 --- /dev/null +++ b/client/ui/qml/Components/EncryptionIndicator.qml @@ -0,0 +1,66 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import Style 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + +import "../Config" + +Rectangle { + id: root + + property string textColor: AmneziaStyle.color.paleGray + + property string textString + property int textFormat: Text.PlainText + + property string iconPath + property real iconWidth: 16 + property real iconHeight: 16 + + color: AmneziaStyle.color.onyxBlack + radius: 32 + implicitHeight: iconHeight + 8 + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 + + RowLayout { + id: content + width: parent.width + anchors.fill: parent + + anchors.leftMargin: content.width / 4 + anchors.rightMargin: content.width / 4 + anchors.topMargin: 4 + anchors.bottomMargin: 4 + + spacing: 0 + + Image { + Layout.alignment: Qt.AlignTop + + width: iconWidth + height: iconHeight + + source: iconPath + } + + CaptionTextType { + id: supportingText + + Layout.fillWidth: true + Layout.leftMargin: 8 + + text: textString + textFormat: root.textFormat + color: textColor + } + } +} \ No newline at end of file diff --git a/client/ui/qml/Components/PasswordDrawer.qml b/client/ui/qml/Components/PasswordDrawer.qml new file mode 100644 index 000000000..652a8d525 --- /dev/null +++ b/client/ui/qml/Components/PasswordDrawer.qml @@ -0,0 +1,89 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + +import "../Config" + +DrawerType2 { + id: root + objectName: "passwordDrawer" + + property var securedFunc + + expandedStateContent: ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.bottomMargin: 8 + + text: qsTr("Enter password to continue") + } + + TextFieldWithHeaderType { + id: passwordField + + property bool hideContent: true + + Layout.fillWidth: true + + headerText: qsTr("Password") + textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal + textField.text: textField.text + + rightButtonClickedOnEnter: true + + clickedFunc: function () { + hideContent = !hideContent + buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + } + + textField.onFocusChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + } + + textField.onTextChanged: { + buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + } + } + + LabelTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.bottomMargin: 16 + + text: SettingsController.getHint() + } + + BasicButtonType { + id: doneButton + + Layout.fillWidth: true + + text: qsTr("Done") + + clickedFunc: function() { + if (passwordField.textField.text !== SettingsController.getPassword()) { + passwordField.errorText = qsTr("Incorrect password") + return + } + + if (root.securedFunc && typeof root.securedFunc === "function") { + root.securedFunc() + } + + root.closeTriggered() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsAppEncryption.qml b/client/ui/qml/Pages2/PageSettingsAppEncryption.qml new file mode 100644 index 000000000..8c5900b8c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsAppEncryption.qml @@ -0,0 +1,148 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + property bool isChangingPassword: false + + Connections { + target: SettingsController + + function onFileEncryptionStateChanged() { + PageController.showBusyIndicator(true) + PageController.closePage() + PageController.goToPage(PageEnum.PageSettingsAppEncryption) + PageController.showBusyIndicator(false) + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + + backButtonFunction: function() { + PageController.closePage() + if (root.isChangingPassword) { + root.isChangingPassword = false + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 + + headerText: qsTr("File encryption") + descriptionText: qsTr("For encrypting backups, configuration files, subscription keys, and logs") + } + + EncryptionIndicator { + id: indicator + + textString: SettingsController.isFileEncryptionEnabled() ? qsTr("Password set. Encryption on") : qsTr("Password not set. Encryption off") + iconPath: SettingsController.isFileEncryptionEnabled() ? "qrc:/images/controls/lock-locked.svg" : "qrc:/images/controls/lock-unlocked.svg" + } + + + BasicButtonType { + id: switchEncryptionButton + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: SettingsController.isFileEncryptionEnabled() ? qsTr("Turn off encryption") : qsTr("Turn on encryption") + + clickedFunc: function() { + SettingsController.isFileEncryptionEnabled() ? SettingsController.toggleFileEncryption(false) + : SettingsController.toggleFileEncryption(true) + } + } + + BasicButtonType { + id: changePasswordButton + + hoveredColor: AmneziaStyle.color.slateGray + defaultColor: AmneziaStyle.color.midnightBlack + textColor: AmneziaStyle.color.paleGray + borderWidth: 1 + + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Change password") + + signal changingPassword + + clickedFunc: function() { + passwordDrawer.openTriggered() + } + } + + PasswordDrawer { + id: passwordDrawer + + parent: root + + anchors.fill: parent + expandedHeight: root.height * 0.45 + + securedFunc: function() { + root.isChangingPassword = true + + PageController.showBusyIndicator(true) + PageController.closePage() + PageController.goToPage(PageEnum.PageSettingsAppPassword) + PageController.showBusyIndicator(false) + + SettingsController.changingPassword() + } + } + } + + spacing: 16 + + footer: ColumnLayout { + width: listView.width + + // TODO: add text + } + } +} \ No newline at end of file diff --git a/client/ui/qml/Pages2/PageSettingsAppPassword.qml b/client/ui/qml/Pages2/PageSettingsAppPassword.qml new file mode 100644 index 000000000..bac19b956 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsAppPassword.qml @@ -0,0 +1,189 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + property bool isChangingPassword: false + + Connections { + target: SettingsController + + function onChangingPassword() { + root.isChangingPassword = true + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + + backButtonFunction: function() { + PageController.closePage() + if (root.isChangingPassword) { + root.isChangingPassword = false + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 + + headerText: root.isChangingPassword ? qsTr("Password changing") : qsTr("File encryption") + descriptionText: root.isChangingPassword ? qsTr("Files encrypted with old password will stay encrypted with old password") + : qsTr("For encrypting backups, configuration files, subscription keys, and logs") + } + } + + model: inputFields + spacing: 16 + + delegate: ColumnLayout { + width: listView.width + + TextFieldWithHeaderType { + id: delegate + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: title + textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal + textField.placeholderText: placeholderContent + textField.text: textField.text + + rightButtonClickedOnEnter: true + + clickedFunc: function () { + clickedHandler() + buttonImageSource = textField.text !== "" ? imageSource : "" + } + + textField.onFocusChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + } + + textField.onTextChanged: { + buttonImageSource = textField.text !== "" ? imageSource : "" + } + } + } + + footer: ColumnLayout { + width: listView.width + + BasicButtonType { + id: continueButton + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Continue") + + clickedFunc: function() { + if (!root.isPasswordProperlyFilled()) { + return + } + + var _password = listView.itemAtIndex(vars.passwordIndex).children[0].textField.text + var _hint = listView.itemAtIndex(vars.hintIndex).children[0].textField.text + + SettingsController.setPassword(_password) + SettingsController.setHint(_hint) + + PageController.goToPage(PageEnum.PageSettingsAppPasswordConfirm) + if (root.isChangingPassword) { + SettingsController.changingPassword() + } + } + } + } + } + + function isPasswordProperlyFilled() { + var tooShort = false + + var secretDataItem = listView.itemAtIndex(vars.passwordIndex).children[0] + if (secretDataItem.textField.text === "") { + secretDataItem.errorText = qsTr("Password cannot be empty") + tooShort = true + } else if (secretDataItem.textField.text.length < 4) { + secretDataItem.errorText = qsTr("Password too short") + tooShort = true + } + + return !tooShort + } + + property list inputFields: [ + passwordObject, + hintObject + ] + + QtObject { + id: passwordObject + + property string title: root.isChangingPassword ? qsTr("New password") : qsTr("Set encryption password") + readonly property string placeholderContent: "" + property string imageSource: "qrc:/images/controls/eye-new.svg" + property bool hideContent: true + readonly property var clickedHandler: function() { + hideContent = !hideContent + imageSource = hideContent ? "qrc:/images/controls/eye-new.svg" : "qrc:/images/controls/eye-off-new.svg" + } + } + + QtObject { + id: hintObject + + property string title: qsTr("Hint for password") + readonly property string placeholderContent: "" + property string imageSource: "" + property bool hideContent: false + readonly property var clickedHandler: undefined + } + + QtObject { + id: vars + + readonly property int passwordIndex: 0 + readonly property int hintIndex: 1 + } +} \ No newline at end of file diff --git a/client/ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml b/client/ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml new file mode 100644 index 000000000..2fc1768a2 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml @@ -0,0 +1,148 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + property bool isChangingPassword: false + + Connections { + target: SettingsController + + function onChangingPassword() { + root.isChangingPassword = true + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: root.isChangingPassword ? qsTr("Confirm new password") : qsTr("Confirm password") + descriptionText: qsTr("If you forget your password, you'll have to reset all app settings to reset it." + + " Encrypted files will remain encrypted") + } + } + + model: 1 // fake model + spacing: 16 + + delegate: ColumnLayout { + width: listView.width + + TextFieldWithHeaderType { + id: delegate + + property bool hideContent: true + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: root.isChangingPassword ? qsTr("Enter new password one more time") : qsTr("Enter password one more time") + textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal + textField.placeholderText: "" + textField.text: textField.text + + rightButtonClickedOnEnter: true + + clickedFunc: function () { + hideContent = !hideContent + buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + } + + textField.onFocusChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + } + + textField.onTextChanged: { + buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : "" + } + } + } + + footer: ColumnLayout { + width: listView.width + + LabelTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 24 + + text: SettingsController.getHint() + } + + BasicButtonType { + id: continueButton + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: root.isChangingPassword ? qsTr("Save new password") : qsTr("Turn on encryption") + + clickedFunc: function() { + if (!root.isPasswordProperlyFilled()) { + return + } + + PageController.closePage() + PageController.goToPage(PageEnum.PageSettings) + PageController.goToPage(PageEnum.PageSettingsAppEncryption) + SettingsController.toggleFileEncryption(true) + } + } + } + } + + function isPasswordProperlyFilled() { + var notMatch = false + + var secretDataItem = listView.itemAtIndex(0).children[0] + if (secretDataItem.textField.text !== SettingsController.getPassword()) { + secretDataItem.errorText = qsTr("Passwords not match") + notMatch = true + } + + return !notMatch + } +} \ No newline at end of file diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 6cb48ac9d..206ce3ada 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -190,6 +190,23 @@ PageType { DividerType {} + LabelWithButtonType { + id: labelWithButtonAppPassword + + Layout.fillWidth: true + + text: qsTr("File encryption") + descriptionText: qsTr("For encrypting backups, configuration files, subscription keys, and logs") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + SettingsController.getPassword() === "" ? PageController.goToPage(PageEnum.PageSettingsAppPassword) + : PageController.goToPage(PageEnum.PageSettingsAppEncryption) + } + } + + DividerType {} + LabelWithButtonType { id: labelWithButtonLogging