Compare commits

...

27 Commits

Author SHA1 Message Date
MrMirDan
6f3c372c9d update: added "Hint" text to password confirm page 2026-03-25 11:08:51 +02:00
MrMirDan
d5616615d3 update: remade EncryptionIndicator 2026-03-24 16:05:44 +02:00
MrMirDan
3ddd38f58e fix: textField image become bigger when error text visible 2026-03-24 13:12:03 +02:00
MrMirDan
50801dd559 update: hint text 2026-03-23 14:05:06 +02:00
MrMirDan
465b3d4d95 fix: value and setValue usage 2026-03-20 15:37:51 +02:00
MrMirDan
23de0c7ed1 Merge branch 'feat/file-password-protection' of https://github.com/amnezia-vpn/amnezia-client into feat/file-password-protection 2026-03-20 15:22:04 +02:00
MrMirDan
18b8f50d21 fix: Sec/ field added to backup 2026-03-20 15:21:37 +02:00
MrMirDan
23dc5e7999 Merge branch 'dev' into feat/file-password-protection 2026-03-19 14:07:27 +02:00
MrMirDan
ae1bbb2f88 fix: restoring from encrypted backup 2026-03-19 14:01:46 +02:00
MrMirDan
bfcb7bf979 update: changed encryption to aes-256-gcm 2026-03-17 16:15:53 +02:00
MrMirDan
dbce2f796c update: app password and encription pages margins 2026-03-13 13:30:42 +02:00
MrMirDan
91307cf49a update: systemController 2026-03-13 13:30:03 +02:00
MrMirDan
e044507b94 Merge branch 'dev' into feat/file-password-protection 2026-01-30 14:45:36 +02:00
MrMirDan
10de29217b update: text and RU translation 2025-12-26 12:00:30 +02:00
MrMirDan
f8c80c21c9 update: text 2025-12-23 16:30:05 +02:00
MrMirDan
805d594608 updated: added encryption docs links 2025-12-23 14:51:34 +02:00
MrMirDan
452150bfff update: text and some fixes 2025-12-23 14:10:19 +02:00
MrMirDan
4c082654f9 fix: correctly save password on set or change 2025-12-19 13:24:51 +02:00
MrMirDan
d8bf7d4d1a update: added encryption indicator on backup, share and api configs pages 2025-12-19 12:58:16 +02:00
MrMirDan
4097af8d81 update: logging page text 2025-12-19 12:40:43 +02:00
MrMirDan
d1cd2a9d8d update: files encrypt on export and files data decrypt on import 2025-12-19 12:24:37 +02:00
MrMirDan
6ebb942466 update: encryption/decryption logic, backup encryption on save and decryption on restore 2025-12-18 12:25:55 +02:00
MrMirDan
ad5d60f915 update: ui to set/change password and hint 2025-12-12 16:32:16 +02:00
MrMirDan
9ed920b715 update: changed password and hint saving 2025-12-11 14:20:13 +02:00
MrMirDan
a868702be0 update: changed logic, added to settingsController 2025-12-09 13:50:55 +02:00
MrMirDan
a9ab281221 removed unnecessary method 2025-10-22 13:17:22 +03:00
MrMirDan
fb9844cab8 feat: file password protection v1 2025-10-22 12:35:14 +03:00
26 changed files with 1633 additions and 81 deletions

View File

@@ -0,0 +1,3 @@
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 5V3C2.5 2.33696 2.76339 1.70107 3.23223 1.23223C3.70107 0.763392 4.33696 0.5 5 0.5C5.66304 0.5 6.29893 0.763392 6.76777 1.23223C7.23661 1.70107 7.5 2.33696 7.5 3V5M1.5 5H8.5C9.05229 5 9.5 5.44772 9.5 6V9.5C9.5 10.0523 9.05229 10.5 8.5 10.5H1.5C0.947715 10.5 0.5 10.0523 0.5 9.5V6C0.5 5.44772 0.947715 5 1.5 5Z" stroke="#EB5757" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@@ -0,0 +1,3 @@
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 5.00252V3.00252C2.49938 2.38254 2.72914 1.78445 3.14469 1.32435C3.56023 0.864252 4.13191 0.574969 4.74875 0.512663C5.36559 0.450356 5.98357 0.61947 6.48274 0.987175C6.9819 1.35488 7.32663 1.89494 7.45 2.50252M1.5 5.00252H8.5C9.05229 5.00252 9.5 5.45023 9.5 6.00252V9.50252C9.5 10.0548 9.05229 10.5025 8.5 10.5025H1.5C0.947715 10.5025 0.5 10.0548 0.5 9.50252V6.00252C0.5 5.45023 0.947715 5.00252 1.5 5.00252Z" stroke="#5CAEE7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@@ -32,6 +32,8 @@
<file>images/controls/history.svg</file>
<file>images/controls/home.svg</file>
<file>images/controls/info.svg</file>
<file>images/controls/lock-locked.svg</file>
<file>images/controls/lock-unlocked.svg</file>
<file>images/controls/mail.svg</file>
<file>images/controls/map-pin.svg</file>
<file>images/controls/more-vertical.svg</file>
@@ -133,10 +135,13 @@
<file>ui/qml/Components/HomeContainersListView.qml</file>
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
<file>ui/qml/Components/PasswordDrawer.qml</file>
<file>ui/qml/Components/QuestionDrawer.qml</file>
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/EncryptionIndicator.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Components/AddSitePanel.qml</file>
<file>ui/qml/Config/GlobalConfig.qml</file>
@@ -208,6 +213,9 @@
<file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file>
<file>ui/qml/Pages2/PageSettingsApplication.qml</file>
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
<file>ui/qml/Pages2/PageSettingsAppEncryption.qml</file>
<file>ui/qml/Pages2/PageSettingsAppPassword.qml</file>
<file>ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml</file>
<file>ui/qml/Pages2/PageSettingsBackup.qml</file>
<file>ui/qml/Pages2/PageSettingsConnection.qml</file>
<file>ui/qml/Pages2/PageSettingsDns.qml</file>

View File

@@ -44,7 +44,7 @@ private:
QStringList encryptedKeys; // encode only key listed here
// only this fields need for backup
QStringList m_fieldsToBackup = {
"Conf/", "Servers/",
"Conf/", "Servers/", "Sec/",
};
mutable QByteArray m_key;

View File

@@ -94,6 +94,33 @@ public:
m_settings.setValue("Conf/startMinimized", enabled);
}
bool isFileEncryption() const
{
return m_settings.value("Sec/fileEncryption", false).toBool();
}
void setFileEncryption(bool enabled)
{
m_settings.setValue("Sec/fileEncryption", enabled);
}
QString getPassword() const
{
return m_settings.value("Sec/password", "").toString();
}
void setPassword(const QString &pwd)
{
m_settings.setValue("Sec/password", pwd);
}
QString getHint() const
{
return m_settings.value("Sec/hint", "").toString();
}
void setHint(const QString &hint)
{
m_settings.setValue("Sec/hint", hint);
}
bool isNewsNotifications() const
{
return m_settings.value("Conf/newsNotifications", true).toBool();

View File

@@ -325,6 +325,14 @@
<translation>Выбрать всё</translation>
</message>
</context>
<context>
<name>EncryptionIndicator</name>
<message>
<location filename="../ui/qml/Components/EncryptionIndicator.qml" line="55"/>
<source> &lt;a href=&quot;learnMore&quot; style=&quot;text-decoration:none; color:%1&quot;&gt;Learn more&lt;/a&gt;</source>
<translation>&lt;a href=&quot;Подробнее&quot; style=&quot;text-decoration:none; color:%1&quot;&gt;Learn more&lt;/a&gt;</translation>
</message>
</context>
<context>
<name>HomeContainersListView</name>
<message>
@@ -1871,67 +1879,72 @@ Thank you for staying with us!</source>
<translation>Для настройки роутера или приложения AmneziaWG</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="73"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="70"/>
<source>Encryption enabled.</source>
<translation>Шифрование включено.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="83"/>
<source>The configuration needs to be reissued</source>
<translation>Необходимо заново скачать конфигурацию и добавить ее в приложение</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="135"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="145"/>
<source> configuration file</source>
<translation> файл конфигурации</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="149"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="159"/>
<source>Generate a new configuration file</source>
<translation>Создать новый файл конфигурации</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="150"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="160"/>
<source>The previously created one will stop working</source>
<translation>Ранее созданный файл перестанет работать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="168"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="178"/>
<source>Revoke the current configuration file</source>
<translation>Отозвать текущий файл конфигурации</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="201"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="211"/>
<source>Config file saved</source>
<translation>Файл конфигурации сохранен</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="215"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="225"/>
<source>The config has been revoked</source>
<translation>Конфигурация была отозвана</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="222"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="232"/>
<source>Generate a new %1 configuration file?</source>
<translation>Создать новый %1 файл конфигурации?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="224"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="234"/>
<source>Revoke the current %1 configuration file?</source>
<translation>Отозвать текущий %1 файл конфигурации?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="227"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="237"/>
<source>Your previous configuration file will no longer work, and it will not be possible to connect using it</source>
<translation>Ваш предыдущий файл конфигурации не будет работать, и вы больше не сможете использовать его для подключения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="228"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="238"/>
<source>Download</source>
<translation>Скачать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="228"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="238"/>
<source>Continue</source>
<translation>Продолжить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="229"/>
<location filename="../ui/qml/Pages2/PageSettingsApiNativeConfigs.qml" line="239"/>
<source>Cancel</source>
<translation>Отменить</translation>
</message>
@@ -2149,6 +2162,180 @@ Thank you for staying with us!</source>
<translation>Скопировано</translation>
</message>
</context>
<context>
<name>PageSettingsAppEncryption</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="27"/>
<source>Encryption enabled</source>
<translation>Шифрование включено</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="27"/>
<source>Encryption disabled</source>
<translation>Шифрование выключено</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="69"/>
<source>Password &amp; Encryption</source>
<translation>Пароль и шифрование</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="70"/>
<source>Password protection for backups and configuration files.
Required to restore or import encrypted files.</source>
<translation>Пароль используется для защиты резервных копий и файлов конфигурации.
Он потребуется при восстановлении или импорте зашифрованных файлов.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="84"/>
<source>Learn more</source>
<translation>Подробнее</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="94"/>
<source>Password set. Encryption enabled</source>
<translation>Пароль задан. Шифрование включено</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="107"/>
<source>Disable encryption</source>
<translation>Выключить шифрование</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="174"/>
<source>If the password is forgotten, it can be recovered. To reset the password, &lt;a href=&quot;appSettings&quot; style=&quot;text-decoration:none; color:%1;&quot;&gt;settings must be reset&lt;/a&gt;.
Encrypted files can only be opened with password used to encrypt them</source>
<translation>Если забудете пароль, восстановить его нельзя. Чтобы сбросить пароль, придется &lt;a href=&quot;appSettings&quot; style=&quot;text-decoration:none; color:%1;&quot;&gt;сбросить все настройки&lt;/a&gt;.
Зашифрованные файлы открываются только тем паролем, которым были зашифрованы</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppEncryption.qml" line="134"/>
<source>Change password</source>
<translation>Измененить пароль</translation>
</message>
</context>
<context>
<name>PageSettingsAppPassword</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="65"/>
<source>Password &amp; Encryption</source>
<translation>Пароль и шифрование</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="65"/>
<source>Change password</source>
<translation>Изменение пароля</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="66"/>
<source>Existing encrypted files will still require the old password.
The new password will be used for new encrypted files.</source>
<translation>Ранее зашифрованные файлы по-прежнему будут требовать старый пароль.
Новый пароль будет использоваться для новых зашифрованных файлов</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="67"/>
<source>Password protection for backups and configuration files.
Required to restore or import encrypted files.</source>
<translation>Пароль используется для защиты резервных копий и файлов конфигурации.
Он потребуется при восстановлении или импорте зашифрованных файлов.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="81"/>
<source>Learn more</source>
<translation>Подробнее</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="93"/>
<source>Password not set. Encryption disabled</source>
<translation>Пароль не задан. Шифрование выключено</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="144"/>
<source>Continue</source>
<translation>Продолжить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="171"/>
<source>Password cannot be empty</source>
<translation>Пароль не может быть пустым</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="174"/>
<source>Password too short</source>
<translation>Пароль слишком короткий</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="189"/>
<source>New password</source>
<translation>Новый пароль</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="189"/>
<source>Set encryption password</source>
<translation>Придумайте пароль шифрования</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="202"/>
<source>New password hint (optional)</source>
<translation>Новая подсказка (необязательно)</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPassword.qml" line="202"/>
<source>Password hint</source>
<translation>Подсказка</translation>
</message>
</context>
<context>
<name>PageSettingsAppPasswordConfirm</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="57"/>
<source>Confirm new password</source>
<translation>Введите новый пароль ещё раз</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="57"/>
<source>Confirm password</source>
<translation>Введите пароль ещё раз</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="58"/>
<source></source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="58"/>
<source>If the password is forgotten, it cant be recovered. To reset the password, the app settings must be reset.
Encrypted files can only be opened with the password used to encrypt them</source>
<translation>Если забудете пароль, восстановить его нельзя. Чтобы сбросить пароль, придется сбросить все настройки.
Зашифрованные файлы открываются только тем паролем, которым были зашифрованы</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="80"/>
<source>Re-enter new password</source>
<translation>Введите новый пароль ещё раз</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="80"/>
<source>Re-enter password</source>
<translation>Введите пароль ещё раз</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="122"/>
<source>Save new password</source>
<translation>Сохранить новый пароль</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="122"/>
<source>Turn on encryption</source>
<translation>Включить шифрование</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppPasswordConfirm.qml" line="149"/>
<source>Passwords not match</source>
<translation>Пароли не совпадают</translation>
</message>
</context>
<context>
<name>PageSettingsAppSplitTunneling</name>
<message>
@@ -2343,63 +2530,68 @@ Thank you for staying with us!</source>
<translation>Вы можете сохранить настройки в файл резервной копии, чтобы восстановить их при следующей установке приложения.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="90"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="81"/>
<source>Encryption enabled.</source>
<translation>Шифрование включено.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="100"/>
<source>The backup will contain your passwords and private keys for all servers added to AmneziaVPN. Keep this information in a secure place.</source>
<translation>Резервная копия будет содержать ваши пароли и закрытые ключи для всех серверов, добавленных в AmneziaVPN. Храните эту информацию в надежном месте.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="104"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="114"/>
<source>Make a backup</source>
<translation>Создать резервную копию</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="111"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="121"/>
<source>Save backup file</source>
<translation>Сохранить резервную копию</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="112"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="145"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="122"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="155"/>
<source>Backup files (*.backup)</source>
<translation>Файлы резервных копий (*.backup)</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="121"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="131"/>
<source>Backup file saved</source>
<translation>Резервная копия сохранена</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="141"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="151"/>
<source>Restore from backup</source>
<translation>Восстановить из резервной копии</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="144"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="154"/>
<source>Open backup file</source>
<translation>Открыть резервную копию</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="155"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="179"/>
<source>Import settings from a backup file?</source>
<translation>Импортировать настройки из резервной копии?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="156"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="180"/>
<source>All current settings will be reset</source>
<translation>Все текущие настройки будут сброшены</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="157"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="181"/>
<source>Continue</source>
<translation>Продолжить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="158"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="182"/>
<source>Cancel</source>
<translation>Отменить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="162"/>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="186"/>
<source>Cannot restore backup settings during active connection</source>
<translation>Невозможно восстановить настройки из резервной копии во время активного соединения</translation>
</message>
@@ -2681,11 +2873,6 @@ Thank you for staying with us!</source>
<source>Logging</source>
<translation>Логирование</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="51"/>
<source>Enabling this function will save application&apos;s logs automatically. By default, logging functionality is disabled. Enable log saving in case of application malfunction.</source>
<translation>Включение этой функции позволяет сохранять логи на вашем устройстве. По умолчанию она отключена. Включите сохранение логов в случае сбоев в работе приложения.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="192"/>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="218"/>
@@ -2704,10 +2891,20 @@ Thank you for staying with us!</source>
<source>Logs file saved</source>
<translation>Файл с логами сохранен</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="51"/>
<source>Logs help diagnose app errors and connection issuesLogging is disabled by default. Enable it when troubleshooting or if requested by support</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="63"/>
<source>Enable logs</source>
<translation>Включить запись логов</translation>
<source>Enable logging</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="80"/>
<source>Delete all logs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="85"/>
@@ -2729,10 +2926,15 @@ Thank you for staying with us!</source>
<source>Logs have been cleaned up</source>
<translation>Логи очищены</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="157"/>
<source>Save logs to file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="181"/>
<source>Client logs</source>
<translation>Логи приложения</translation>
<source>App logs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="182"/>
@@ -2744,11 +2946,6 @@ Thank you for staying with us!</source>
<source>Open logs folder</source>
<translation>Открыть папку с логами</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="157"/>
<source>Export logs</source>
<translation>Сохранить логи</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="210"/>
<source>Service logs</source>
@@ -2759,10 +2956,13 @@ Thank you for staying with us!</source>
<source>AmneziaVPN-service logs</source>
<translation>AmneziaVPN-service logs</translation>
</message>
</context>
<context>
<name>PageSettingsNewsNotifications</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="80"/>
<source>Clear logs</source>
<translation>Очистить логи</translation>
<location filename="../ui/qml/Pages2/PageSettingsNewsNotifications.qml" line="33"/>
<source>News &amp; Notifications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
@@ -3179,77 +3379,77 @@ Thank you for staying with us!</source>
<translation>Файл с настройками подключения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="48"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="49"/>
<source>Connection</source>
<translation>Соединение</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="76"/>
<source>Settings</source>
<translation>Настройки</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="85"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="86"/>
<source>Enable logs</source>
<translation>Включить запись логов</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="99"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="100"/>
<source>Export client logs</source>
<translation>Экспорт логов клиента</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="109"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="110"/>
<source>Save</source>
<translation>Сохранить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="110"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="111"/>
<source>Logs files (*.log)</source>
<translation>Файлы логов (*.log)</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="119"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="120"/>
<source>Logs file saved</source>
<translation>Файл с логами сохранен</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="129"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="130"/>
<source>Support tag</source>
<translation>Support tag</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="140"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="141"/>
<source>Copied</source>
<translation>Скопировано</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="159"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="160"/>
<source>Insert the key, add a configuration file or scan the QR-code</source>
<translation>Вставьте ключ, добавьте файл конфигурации или отсканируйте QR-код</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="169"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="170"/>
<source>Insert key</source>
<translation>Вставьте ключ</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="170"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="171"/>
<source>Insert</source>
<translation>Вставить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="188"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="189"/>
<source>Continue</source>
<translation>Продолжить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="205"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="206"/>
<source>Other connection options</source>
<translation>Другие варианты подключения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="253"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="254"/>
<source>Site Amnezia</source>
<translation>Сайт Amnezia</translation>
</message>
@@ -3279,11 +3479,16 @@ Thank you for staying with us!</source>
<translation>Настроить VPN на собственном сервере</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="315"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="306"/>
<source>Restore from backup</source>
<translation>Восстановить из резервной копии</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="316"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="339"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="363"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="378"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="307"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="325"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="344"/>
@@ -3293,21 +3498,25 @@ Thank you for staying with us!</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="320"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="311"/>
<source>Open backup file</source>
<translation>Открыть резервную копию</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="321"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="312"/>
<source>Backup files (*.backup)</source>
<translation>Файлы резервных копий (*.backup)</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="345"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="331"/>
<source>Open config file</source>
<translation>Открыть файл с конфигурацией</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="362"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="343"/>
<source>QR code</source>
<translation>QR-код</translation>
@@ -3600,8 +3809,13 @@ Thank you for staying with us!</source>
<translation>Соединение</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="323"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="324"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="303"/>
<source>Encryption enabled.</source>
<translation>Шифрование включено.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="333"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="334"/>
<source>Server</source>
<translation>Сервер</translation>
</message>
@@ -3702,7 +3916,7 @@ Thank you for staying with us!</source>
<translation>Пользователи</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="304"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="314"/>
<source>User name</source>
<translation>Имя пользователя</translation>
</message>
@@ -3859,28 +4073,33 @@ Thank you for staying with us!</source>
<translation>Если вы поделитесь полным доступом с другими людьми, то они смогут удалять и добавлять протоколы и сервисы на сервер, что приведет к некорректной работе VPN для всех пользователей. </translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="87"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="88"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="78"/>
<source>Encryption enabled.</source>
<translation>Шифрование включено.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="97"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="98"/>
<source>Server</source>
<translation>Сервер</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="115"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="125"/>
<source>Accessing </source>
<translation>Доступ </translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="116"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="126"/>
<source>File with accessing settings to </source>
<translation>Файл с настройками доступа к </translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="147"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="157"/>
<source>Share</source>
<translation>Поделиться</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="155"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="165"/>
<source>Access error!</source>
<translation>Ошибка доступа!</translation>
</message>
@@ -3903,6 +4122,30 @@ Thank you for staying with us!</source>
<translation>Логирование включено. Обратите внимание, что через 14 дней оно будет автоматически отключено, а все файлы логов будут удалены.</translation>
</message>
</context>
<context>
<name>PasswordDrawer</name>
<message>
<location filename="../ui/qml/Components/PasswordDrawer.qml" line="56"/>
<source>Password required</source>
<translation>Для продолжения введите пароль</translation>
</message>
<message>
<location filename="../ui/qml/Components/PasswordDrawer.qml" line="74"/>
<source>Password</source>
<translation>Пароль</translation>
</message>
<message>
<location filename="../ui/qml/Components/PasswordDrawer.qml" line="107"/>
<source>Done</source>
<translation>Готово</translation>
</message>
<message>
<location filename="../ui/qml/Components/PasswordDrawer.qml" line="112"/>
<location filename="../ui/qml/Components/PasswordDrawer.qml" line="117"/>
<source>Invalid password</source>
<translation>Неверный пароль</translation>
</message>
</context>
<context>
<name>PopupType</name>
<message>

View File

@@ -305,6 +305,8 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey);
SystemController::saveFile(fileName, nativeConfig);
if (m_settings->isFileEncryption())
SystemController::encryptFile(fileName, m_settings->getPassword(), m_settings->getHint());
return true;
}

View File

@@ -346,6 +346,8 @@ QList<QString> ExportController::getQrCodes()
void ExportController::exportConfig(const QString &fileName)
{
SystemController::saveFile(fileName, m_config);
if (m_settings->isFileEncryption())
SystemController::encryptFile(fileName, m_settings->getPassword(), m_settings->getHint());
}
void ExportController::updateClientManagementModel(const DockerContainer container, ServerCredentials credentials)

View File

@@ -34,6 +34,9 @@ namespace PageLoader
PageSettingsSplitTunneling,
PageSettingsAppSplitTunneling,
PageSettingsKillSwitch,
PageSettingsAppEncryption,
PageSettingsAppPassword,
PageSettingsAppPasswordConfirm,
PageSettingsApiServerInfo,
PageSettingsApiAvailableCountries,
PageSettingsApiSupport,

View File

@@ -174,6 +174,8 @@ void SettingsController::backupAppConfig(const QString &fileName)
config["Conf/useAmneziaDns"] = isAmneziaDnsEnabled();
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
if (m_settings->isFileEncryption())
SystemController::encryptFile(fileName, m_settings->getPassword(), m_settings->getHint());
}
void SettingsController::restoreAppConfig(const QString &fileName)
@@ -307,6 +309,57 @@ void SettingsController::toggleStartMinimized(bool enable)
emit startMinimizedChanged();
}
bool SettingsController::isFileEncryptionEnabled()
{
return m_settings->isFileEncryption();
}
void SettingsController::toggleFileEncryption(bool enable)
{
m_settings->setFileEncryption(enable);
emit fileEncryptionStateChanged();
}
void SettingsController::setPassword(QString pwd)
{
m_settings->setPassword(pwd);
}
QString SettingsController::getPassword()
{
return m_settings->getPassword();
}
void SettingsController::setHint(QString hint)
{
m_settings->setHint(hint);
}
QString SettingsController::getHint()
{
return m_settings->getHint();
}
void SettingsController::setTempPassword(QString pwd)
{
tempPassword = pwd;
}
QString SettingsController::getTempPassword()
{
return tempPassword;
}
void SettingsController::setTempHint(QString hint)
{
tempHint = hint;
}
QString SettingsController::getTempHint()
{
return tempHint;
}
bool SettingsController::isNewsNotificationsEnabled()
{
return m_settings->isNewsNotifications();

View File

@@ -73,6 +73,19 @@ public slots:
bool isStartMinimizedEnabled();
void toggleStartMinimized(bool enable);
bool isFileEncryptionEnabled();
void toggleFileEncryption(bool enable);
void setPassword(QString pwd);
QString getPassword();
void setHint(QString hint);
QString getHint();
void setTempPassword(QString pwd);
QString getTempPassword();
void setTempHint(QString hint);
QString getTempHint();
bool isNewsNotificationsEnabled();
void toggleNewsNotificationsEnabled(bool enable);
@@ -144,6 +157,9 @@ signals:
void isHomeAdLabelVisibleChanged(bool visible);
void startMinimizedChanged();
void fileEncryptionStateChanged();
void changingPassword();
private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
@@ -162,6 +178,9 @@ private:
QString getPlatform();
QString tempPassword;
QString tempHint;
QDateTime m_loggingDisableDate;
bool m_isDevModeEnabled = false;

View File

@@ -10,6 +10,21 @@
#include <QUrl>
#include <QtConcurrent>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
namespace
{
constexpr int SALT_LEN = 16;
constexpr int IV_LEN = 12;
constexpr int KEY_LEN = 32;
constexpr int TAG_LEN = 16;
constexpr int PBKDF2_ITER = 100000;
const QByteArray magicString { "EncData" };
}
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
@@ -89,6 +104,313 @@ bool SystemController::readFile(const QString &fileName, QString &data)
return true;
}
static QString opensslErrString()
{
unsigned long e = ERR_get_error();
if (!e)
return QStringLiteral("Unknown OpenSSL error");
char buf[256];
ERR_error_string_n(e, buf, sizeof(buf));
return QString::fromUtf8(buf);
}
static bool deriveKey(const QByteArray &password, const QByteArray &salt, QByteArray &outKey)
{
outKey.resize(KEY_LEN);
const unsigned char *pw = reinterpret_cast<const unsigned char *>(password.constData());
const unsigned char *s = reinterpret_cast<const unsigned char *>(salt.constData());
int ok = PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(pw), password.size(), s, salt.size(), PBKDF2_ITER,
EVP_sha256(), KEY_LEN, reinterpret_cast<unsigned char *>(outKey.data()));
if (!ok) {
qDebug() << opensslErrString();
}
return ok == 1;
}
static bool aesCrypt(const QByteArray &in, const QByteArray &key, const QByteArray &iv, QByteArray &out,
QByteArray &tag, bool encrypt)
{
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX *)> ctx { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
if (!ctx) {
qDebug() << "EVP_CIPHER_CTX_new failed";
return false;
}
const EVP_CIPHER *cipher = EVP_aes_256_gcm();
if (1 != EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, encrypt ? 1 : 0)) {
qDebug() << opensslErrString();
return false;
}
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) {
qDebug() << opensslErrString();
return false;
}
if (1 != EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
reinterpret_cast<const unsigned char *>(iv.constData()), -1)) {
qDebug() << opensslErrString();
return false;
}
out.clear();
out.resize(in.size());
int outlen = 0;
if (1 != EVP_CipherUpdate(ctx.get(), reinterpret_cast<unsigned char *>(out.data()), &outlen,
reinterpret_cast<const unsigned char *>(in.constData()), in.size())) {
qDebug() << opensslErrString();
return false;
}
int tmplen = 0;
if (encrypt) {
if (1 != EVP_CipherFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(out.data()) + outlen, &tmplen)) {
qDebug() << opensslErrString();
return false;
}
out.resize(outlen + tmplen);
tag.resize(TAG_LEN);
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, TAG_LEN, tag.data())) {
qDebug() << opensslErrString();
return false;
}
} else {
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.constData()))) {
qDebug() << opensslErrString();
return false;
}
if (1 != EVP_CipherFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(out.data()) + outlen, &tmplen)) {
qDebug() << "Authentication failed: " << opensslErrString();
return false;
}
out.resize(outlen + tmplen);
}
return true;
}
bool SystemController::encryptFile(const QString &filePath, const QString &password, const QString &hint)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open file for read: " << f.errorString();
return false;
}
QByteArray content = f.readAll();
f.close();
if (content.startsWith(magicString)) {
qDebug() << "File already encrypted";
return false;
}
QByteArray salt(SALT_LEN, 0);
QByteArray iv(IV_LEN, 0);
QByteArray key;
QByteArray cipher;
QByteArray tag;
if (1 != RAND_bytes(reinterpret_cast<unsigned char *>(salt.data()), SALT_LEN)
|| 1 != RAND_bytes(reinterpret_cast<unsigned char *>(iv.data()), IV_LEN)) {
qDebug() << opensslErrString();
return false;
}
if (!deriveKey(password.toUtf8(), salt, key))
return false;
if (!aesCrypt(content, key, iv, cipher, tag, true))
return false;
QByteArray out;
QByteArray hintBytes = hint.toUtf8();
quint32 hintLen = static_cast<quint32>(hintBytes.size());
out += magicString;
out.append(reinterpret_cast<const char *>(&hintLen), sizeof(hintLen));
out += hintBytes;
out += salt;
out += iv;
out += tag;
out += cipher;
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qDebug() << "Cannot open file for write: " << f.errorString();
return false;
}
if (f.write(out) != out.size()) {
qDebug() << "Write failed";
f.close();
return false;
}
f.close();
return true;
}
QByteArray SystemController::getDecryptedData(const QString &filePath, const QString &password)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open file: " << f.errorString();
return {};
}
QByteArray content = f.readAll();
f.close();
if (!content.startsWith(magicString)) {
qDebug() << "Invalid file format (magic missing)";
return {};
}
int pos = magicString.size();
if (content.size() < pos + static_cast<int>(sizeof(quint32))) {
qDebug() << "Corrupted file (no hint length)";
return {};
}
quint32 hintLen = 0;
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
if (content.size() < pos + static_cast<int>(hintLen) + SALT_LEN + IV_LEN + TAG_LEN) {
qDebug() << "Corrupted file (invalid sizes)";
return {};
}
pos += hintLen;
QByteArray salt = content.mid(pos, SALT_LEN);
pos += SALT_LEN;
QByteArray iv = content.mid(pos, IV_LEN);
pos += IV_LEN;
QByteArray tag = content.mid(pos, TAG_LEN);
pos += TAG_LEN;
QByteArray cipher = content.mid(pos);
QByteArray key;
if (!deriveKey(password.toUtf8(), salt, key)) {
qDebug() << "Key derivation failed";
return {};
}
QByteArray plain;
if (!aesCrypt(cipher, key, iv, plain, tag, false)) {
qDebug() << "Decryption failed (wrong password or corrupted data)";
return {};
}
return plain;
}
bool SystemController::isFileEncrypted(const QString &filePath)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open file for read: %1", f.errorString();
return false;
}
QByteArray data = f.readAll();
f.close();
if (!data.startsWith(magicString)) {
qDebug() << "File is not recognized as encrypted (magic missing)";
return false;
}
return true;
}
bool SystemController::isPasswordValid(const QString &filePath, const QString &password)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << f.errorString();
return false;
}
QByteArray content = f.readAll();
f.close();
if (!content.startsWith(magicString))
return false;
int pos = magicString.size();
quint32 hintLen = 0;
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
pos += hintLen;
QByteArray salt = content.mid(pos, SALT_LEN);
pos += SALT_LEN;
QByteArray iv = content.mid(pos, IV_LEN);
pos += IV_LEN;
QByteArray tag = content.mid(pos, TAG_LEN);
pos += TAG_LEN;
QByteArray cipher = content.mid(pos);
QByteArray key;
if (!deriveKey(password.toUtf8(), salt, key))
return false;
QByteArray plain;
bool ok = aesCrypt(cipher, key, iv, plain, tag, false);
if (!ok)
qDebug() << "Wrong password";
return ok;
}
QString SystemController::readHint(const QString &filePath)
{
if (filePath.isEmpty())
return "";
QByteArray data;
readFile(filePath, data);
if (!data.startsWith(magicString)) {
qDebug() << "Not an encrypted file";
return {};
}
int pos = magicString.size();
if (data.size() < pos + static_cast<int>(sizeof(quint32))) {
qDebug() << "Corrupted file (no hint length)";
return {};
}
quint32 hintLen = 0;
memcpy(&hintLen, data.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
if (data.size() < pos + static_cast<int>(hintLen)) {
qDebug() << "Corrupted file (hint truncated)";
return {};
}
return QString::fromUtf8(data.constData() + pos, hintLen);
}
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
{

View File

@@ -15,10 +15,18 @@ public:
static bool readFile(const QString &fileName, QByteArray &data);
static bool readFile(const QString &fileName, QString &data);
static bool encryptFile(const QString &filePath, const QString &password, const QString &hint);
public slots:
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
const bool isSaveMode = false, const QString &defaultSuffix = "");
QByteArray getDecryptedData(const QString &filePath, const QString &password);
bool isFileEncrypted(const QString &filePath);
bool isPasswordValid(const QString &filePath, const QString &password);
QString readHint(const QString &filePath);
void setQmlRoot(QObject *qmlRoot);
bool isAuthenticated();

View File

@@ -0,0 +1,86 @@
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 bool linkEnabled: false
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
anchors.centerIn: parent
spacing: 0
Image {
width: root.iconWidth
height: root.iconHeight
source: root.iconPath
}
CaptionTextType {
id: supportingText
Layout.fillWidth: true
Layout.leftMargin: 8
text: root.textString
textFormat: Text.RichText
color: root.textColor
}
BasicButtonType {
visible: root.linkEnabled
hoverEnabled: false
implicitHeight: root.iconHeight
implicitWidth: linkText.width/2 + 8
defaultColor: AmneziaStyle.color.transparent
CaptionTextType {
id: linkText
leftPadding: 4
width: linkText.text.length * linkText.font.pixelSize
text: qsTr("Learn more")
textFormat: Text.RichText
color: AmneziaStyle.color.goldenApricot
}
clickedFunc: function() {
Qt.openUrlExternally("https://storage.googleapis.com/amnezia/docs?m-path=/documentation/instructions/encryption")
}
}
}
}

View File

@@ -0,0 +1,130 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
objectName: "passwordDrawer"
property bool fromOutside: true
property string fileName
property var securedFunc
signal restoreSecuredBackup
signal importSecuredFile
expandedStateContent: ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
Connections {
target: root
function onRestoreSecuredBackup() {
root.securedFunc = function() {
SettingsController.restoreAppConfigFromData(SystemController.getDecryptedData(fileName, passwordField.textField.text))
}
passwordDrawer.openTriggered()
}
function onImportSecuredFile() {
root.securedFunc = function() {
if (ImportController.extractConfigFromData(SystemController.getDecryptedData(fileName, passwordField.textField.text))) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
}
passwordDrawer.openTriggered()
}
}
Header2TextType {
Layout.fillWidth: true
Layout.bottomMargin: 8
text: qsTr("Password required")
}
TextFieldWithHeaderType {
id: passwordField
Connections {
target: root
function onCloseTriggered() {
passwordField.textField.text = ""
}
}
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: qsTr("Hint: ") + (fromOutside ? SystemController.readHint(fileName) : SettingsController.getHint())
}
BasicButtonType {
id: doneButton
Layout.fillWidth: true
text: qsTr("Done")
clickedFunc: function() {
if (fromOutside) {
if (!SystemController.isPasswordValid(fileName, passwordField.textField.text)) {
passwordField.errorText = qsTr("Invalid password")
return
}
} else {
if (passwordField.textField.text !== SettingsController.getPassword()) {
passwordField.errorText = qsTr("Invalid password")
return
}
}
if (root.securedFunc && typeof root.securedFunc === "function") {
root.securedFunc()
}
root.closeTriggered()
}
}
}
}

View File

@@ -188,11 +188,10 @@ Item {
leftImageSource: root.buttonImageSource
anchors.top: content.top
anchors.bottom: content.bottom
anchors.right: content.right
height: content.implicitHeight
width: content.implicitHeight
height: root.errorText !== "" ? content.implicitHeight - errorField.height - 5: content.implicitHeight
width: root.errorText !== "" ? content.implicitHeight - errorField.height - 5: content.implicitHeight
squareLeftSide: true
clickedFunc: function() {

View File

@@ -60,6 +60,16 @@ PageType {
headerText: qsTr("Configuration Files")
descriptionText: qsTr("For router setup or the AmneziaWG app")
}
EncryptionIndicator {
id: indicator
visible: SettingsController.isFileEncryptionEnabled()
linkEnabled: true
textString: qsTr("Encryption enabled.")
iconPath: "qrc:/images/controls/lock-locked.svg"
}
}
delegate: ColumnLayout {

View File

@@ -0,0 +1,187 @@
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()
SettingsController.isFileEncryptionEnabled() ? PageController.goToPage(PageEnum.PageSettingsAppEncryption) : PageController.goToPage(PageEnum.PageSettingsAppPassword)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(SettingsController.isFileEncryptionEnabled() ? qsTr("Encryption enabled") : qsTr("Encryption disabled"))
}
}
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
headerText: qsTr("Password & Encryption")
descriptionText: qsTr("Password protection for backups and configuration files.\nRequired to restore or import encrypted files.")
}
BasicButtonType {
Layout.leftMargin: 8
Layout.bottomMargin: 32
implicitHeight: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Learn more")
clickedFunc: function() {
Qt.openUrlExternally("https://storage.googleapis.com/amnezia/docs?m-path=/documentation/instructions/encryption")
}
}
EncryptionIndicator {
id: indicator
textString: qsTr("Password set. Encryption enabled")
iconPath: "qrc:/images/controls/lock-locked.svg"
}
BasicButtonType {
id: disableEncryptionButton
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Disable encryption")
clickedFunc: function() {
passwordDrawer.securedFunc = function() {
PageController.showBusyIndicator(true)
SettingsController.toggleFileEncryption(false)
SettingsController.setPassword("")
SettingsController.setHint("")
PageController.showBusyIndicator(false)
}
passwordDrawer.openTriggered()
}
}
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")
clickedFunc: function() {
passwordDrawer.securedFunc = function() {
root.isChangingPassword = true
PageController.showBusyIndicator(true)
PageController.closePage()
PageController.goToPage(PageEnum.PageSettingsAppPassword)
PageController.showBusyIndicator(false)
SettingsController.changingPassword()
}
passwordDrawer.openTriggered()
}
}
PasswordDrawer {
id: passwordDrawer
fromOutside: false
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.45
}
}
spacing: 16
footer: ColumnLayout {
width: listView.width
CaptionTextType {
Layout.fillWidth: true
Layout.topMargin: 32
horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText
text: qsTr("If the password is forgotten, it can be recovered. To reset the password, "
+ "<a href=\"appSettings\" style=\"text-decoration:none; color:%1;\">settings must be reset</a>."
+ "\nEncrypted files can only be opened with password used to encrypt them").arg(AmneziaStyle.color.goldenApricot)
color: AmneziaStyle.color.mutedGray
onLinkActivated: function(link) {
if (link === "appSettings") {
PageController.closePage()
}
}
}
}
}
}

View File

@@ -0,0 +1,215 @@
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
headerText: root.isChangingPassword ? qsTr("Change password") : qsTr("Password & Encryption")
descriptionText: root.isChangingPassword ? qsTr("Existing encrypted files will still require the old password.\nThe new password will be used for new encrypted files.")
: qsTr("Password protection for backups and configuration files.\nRequired to restore or import encrypted files.")
}
BasicButtonType {
Layout.leftMargin: 8
Layout.bottomMargin: 32
implicitHeight: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Learn more")
clickedFunc: function() {
Qt.openUrlExternally("https://storage.googleapis.com/amnezia/docs?m-path=/documentation/instructions/encryption")
}
}
EncryptionIndicator {
id: indicator
visible: !root.isChangingPassword
textString: qsTr("Password not set. Encryption disabled")
iconPath: "qrc:/images/controls/lock-unlocked.svg"
}
}
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.setTempPassword(_password)
SettingsController.setTempHint(_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<QtObject> 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.svg"
property bool hideContent: true
readonly property var clickedHandler: function() {
hideContent = !hideContent
imageSource = hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg"
}
}
QtObject {
id: hintObject
property string title: root.isChangingPassword ? qsTr("New password hint (optional)") : qsTr("Password hint")
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
}
}

View File

@@ -0,0 +1,155 @@
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: root.isChangingPassword ? qsTr("") : qsTr("If the password is forgotten, it cant be recovered. "
+ "To reset the password, the app settings must be reset.\n"
+ "Encrypted files can only be opened with the password used to encrypt them")
}
}
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("Re-enter new password") : qsTr("Re-enter password")
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: qsTr("Hint: ") + SettingsController.getTempHint()
}
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
}
SettingsController.setPassword(SettingsController.getTempPassword())
SettingsController.setHint(SettingsController.getTempHint())
SettingsController.setTempPassword("")
SettingsController.setTempHint("")
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.getTempPassword()) {
secretDataItem.errorText = qsTr("Passwords not match")
notMatch = true
}
return !notMatch
}
}

View File

@@ -213,6 +213,23 @@ PageType {
DividerType {}
LabelWithButtonType {
id: labelWithButtonAppPassword
Layout.fillWidth: true
text: qsTr("Password & Encryption")
descriptionText: qsTr("Password protection for backups and configuration files")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
SettingsController.getPassword() === "" ? PageController.goToPage(PageEnum.PageSettingsAppPassword)
: PageController.goToPage(PageEnum.PageSettingsAppEncryption)
}
}
DividerType {}
LabelWithButtonType {
id: labelWithButtonLogging

View File

@@ -71,6 +71,16 @@ PageType {
headerText: qsTr("Back up your configuration")
descriptionText: qsTr("You can save your settings to a backup file to restore them the next time you install the application.")
}
EncryptionIndicator {
id: indicator
visible: SettingsController.isFileEncryptionEnabled()
linkEnabled: true
textString: qsTr("Encryption enabled.")
iconPath: "qrc:/images/controls/lock-locked.svg"
}
}
model: 1 // fake model to force the ListView to be created without a model
@@ -144,10 +154,20 @@ PageType {
var filePath = SystemController.getFileName(qsTr("Open backup file"),
qsTr("Backup files (*.backup)"))
if (filePath !== "") {
restoreBackup(filePath)
passwordDrawer.fileName = filePath
SystemController.isFileEncrypted(filePath) ? passwordDrawer.restoreSecuredBackup() : restoreBackup(filePath)
}
}
}
PasswordDrawer {
id: passwordDrawer
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.45
}
}
}

View File

@@ -48,8 +48,8 @@ PageType {
Layout.rightMargin: 16
headerText: qsTr("Logging")
descriptionText: qsTr("Enabling this function will save application's logs automatically. " +
"By default, logging functionality is disabled. Enable log saving in case of application malfunction.")
descriptionText: qsTr("Logs help diagnose app errors and connection issues" +
"Logging is disabled by default. Enable it when troubleshooting or if requested by support")
}
SwitcherType {
@@ -60,7 +60,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Enable logs")
text: qsTr("Enable logging")
checked: SettingsController.isLoggingEnabled
@@ -77,7 +77,7 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: -8
text: qsTr("Clear logs")
text: qsTr("Delete all logs")
leftImageSource: "qrc:/images/controls/trash.svg"
isSmallLeftImage: true
@@ -154,7 +154,7 @@ PageType {
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Export logs")
text: qsTr("Save logs to file")
leftImageSource: "qrc:/images/controls/save.svg"
isSmallLeftImage: true
@@ -178,7 +178,7 @@ PageType {
QtObject {
id: clientLogs
readonly property string title: qsTr("Client logs")
readonly property string title: qsTr("App logs")
readonly property string description: qsTr("AmneziaVPN logs")
readonly property bool isVisible: true
readonly property var openLogsHandler: function() {

View File

@@ -12,6 +12,7 @@ import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
@@ -261,6 +262,15 @@ PageType {
}
}
PasswordDrawer {
id: passwordDrawer
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.45
}
property list<QtObject> variants: [
amneziaVpn,
selfHostVpn,
@@ -312,7 +322,12 @@ PageType {
qsTr("Backup files (*.backup)"))
if (filePath !== "") {
PageController.showBusyIndicator(true)
SettingsController.restoreAppConfig(filePath)
if (SystemController.isFileEncrypted(filePath)) {
passwordDrawer.fileName = filePath
passwordDrawer.restoreSecuredBackup()
} else {
SettingsController.restoreAppConfig(filePath)
}
PageController.showBusyIndicator(false)
}
}
@@ -330,8 +345,13 @@ PageType {
"Config files (*.vpn *.ovpn *.conf *.json)"
var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
if (fileName !== "") {
if (ImportController.extractConfigFromFile(fileName)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
if (SystemController.isFileEncrypted(fileName)) {
passwordDrawer.fileName = fileName
passwordDrawer.importSecuredFile()
} else {
if (ImportController.extractConfigFromFile(fileName)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
}
}
}

View File

@@ -294,6 +294,16 @@ PageType {
color: AmneziaStyle.color.mutedGray
}
EncryptionIndicator {
id: indicator
visible: SettingsController.isFileEncryptionEnabled()
linkEnabled: true
textString: qsTr("Encryption enabled.")
iconPath: "qrc:/images/controls/lock-locked.svg"
}
TextFieldWithHeaderType {
id: clientNameTextField
Layout.fillWidth: true

View File

@@ -69,6 +69,16 @@ PageType {
color: AmneziaStyle.color.mutedGray
}
EncryptionIndicator {
id: indicator
visible: SettingsController.isFileEncryptionEnabled()
linkEnabled: true
textString: qsTr("Encryption enabled.")
iconPath: "qrc:/images/controls/lock-locked.svg"
}
DropDownType {
id: serverSelector
objectName: "serverSelector"