Compare commits

..

5 Commits

Author SHA1 Message Date
dranik 4cf0e477c1 merge dev 2026-05-18 18:43:12 +03:00
dranik 426a95c425 fixed compile file 2026-05-05 15:20:23 +03:00
dranik 5e52f7fcb0 fix path include 2026-05-05 15:05:07 +03:00
dranik c9a1b2e451 fix build iOS 2026-05-05 14:57:36 +03:00
dranik 6087375fb0 Fixed: push notifications on VPN connect/disconnect 2026-05-04 23:38:27 +03:00
67 changed files with 367 additions and 1619 deletions
+24 -42
View File
@@ -18,6 +18,7 @@ jobs:
- uses: dorny/paths-filter@v3
id: filter
with:
base: ${{ github.event.before }}
filters: |
recipes:
- 'recipes/**'
@@ -39,7 +40,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build dependencies'
shell: bash
@@ -49,11 +50,9 @@ jobs:
done
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
- name: 'Upload baked prebuilts'
if: github.ref == 'refs/heads/dev'
run: conan upload -r amnezia "*" -c
# ------------------------------------------------------
@@ -99,7 +98,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Install system packages'
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
@@ -119,7 +118,7 @@ jobs:
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v7
with:
path: deploy/build/AmneziaVPN_*_linux_x64.run
path: deploy/build/AmneziaVPN-*-Linux.run
archive: false
retention-days: 7
@@ -150,17 +149,15 @@ jobs:
- uses: ilammy/msvc-dev-cmd@v1
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build dependencies'
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
- name: 'Upload baked prebuilts'
if: github.ref == 'refs/heads/dev'
run: conan upload -r amnezia "*" -c
# ------------------------------------------------------
@@ -232,7 +229,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build project'
shell: cmd
@@ -245,31 +242,27 @@ jobs:
- name: 'Upload WIX installer artifact'
uses: actions/upload-artifact@v7
with:
path: deploy/build/AmneziaVPN_*_windows_x64.msi
path: deploy/build/AmneziaVPN-*-win64.msi
archive: false
retention-days: 7
- name: 'Upload IFW installer artifact'
uses: actions/upload-artifact@v7
with:
path: deploy/build/AmneziaVPN_*_windows_x64.exe
path: deploy/build/AmneziaVPN-*-win64.exe
archive: false
retention-days: 7
# ------------------------------------------------------
Bake-Prebuilts-iOS:
runs-on: macos-latest
needs: Detect-Changes
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
strategy:
matrix:
xcode-version: [26.0, 26.4]
include:
- xcode-version: 26.4
os: macos-26
runs-on: ${{ matrix.os || 'macos-latest' }}
xcode-version: [26.0]
steps:
- uses: actions/checkout@v4
@@ -286,17 +279,15 @@ jobs:
xcode-version: ${{ matrix.xcode-version }}
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build dependencies'
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
- name: 'Upload baked prebuilts'
if: github.ref == 'refs/heads/dev'
run: conan upload -r amnezia "*" -c
# ------------------------------------------------------
@@ -353,7 +344,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.0'
xcode-version: '26.1'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -385,7 +376,7 @@ jobs:
python-version: 3.14
- name: 'Install deps'
run: pip install "conan==2.28.0" jsonschema jinja2
run: pip install "conan==2.26.2" jsonschema jinja2
- name: 'Build project'
env:
@@ -403,17 +394,14 @@ jobs:
# ------------------------------------------------------
Bake-Prebuilts-MacOS:
runs-on: macos-latest
needs: Detect-Changes
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
strategy:
matrix:
xcode-version: [16.2, 16.4, 26.4]
include:
- xcode-version: 26.4
os: macos-26
runs-on: ${{ matrix.os || 'macos-latest' }}
xcode-version: [16.2, 16.4]
steps:
- uses: actions/checkout@v4
@@ -430,17 +418,15 @@ jobs:
xcode-version: ${{ matrix.xcode-version }}
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build dependencies'
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
- name: 'Upload baked prebuilts'
if: github.ref == 'refs/heads/dev'
run: conan upload -r amnezia "*" -c
# ------------------------------------------------------
@@ -516,7 +502,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build project'
env:
@@ -532,7 +518,7 @@ jobs:
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v7
with:
path: deploy/build/AmneziaVPN_*_macos_x64.pkg
path: deploy/build/AmneziaVPN-*-Darwin.pkg
archive: false
retention-days: 7
@@ -562,17 +548,15 @@ jobs:
xcode-version: ${{ matrix.xcode-version }}
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build dependencies'
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DMACOS_NE=TRUE
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
- name: 'Upload baked prebuilts'
if: github.ref == 'refs/heads/dev'
run: conan upload -r amnezia "*" -c
# ------------------------------------------------------
@@ -651,7 +635,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Build project'
run: |
@@ -687,7 +671,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Setup Android SDK'
uses: android-actions/setup-android@v4
@@ -712,11 +696,9 @@ jobs:
done
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
- name: 'Upload baked prebuilts'
if: github.ref == 'refs/heads/dev'
run: conan upload -r amnezia "*" -c
# ------------------------------------------------------
@@ -730,7 +712,7 @@ jobs:
env:
ANDROID_PLATFORM: android-28
NDK_VERSION: 27.0.11718014
QT_VERSION: 6.10.3
QT_VERSION: 6.10.1
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -824,7 +806,7 @@ jobs:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.28.0"
run: pip install "conan==2.26.2"
- name: 'Decode keystore secret to file'
env:
+2 -2
View File
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.0)
set(AMNEZIAVPN_VERSION 4.8.15.4)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2122)
set(APP_ANDROID_VERSION_CODE 2120)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
+3
View File
@@ -24,5 +24,8 @@
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
<string name="vpnStateEventChannelName">Уведомления о VPN</string>
<string name="vpnStateEventChannelDescription">Краткие оповещения при подключении и отключении VPN</string>
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
</resources>
+3
View File
@@ -24,5 +24,8 @@
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
<string name="openNotificationSettings">Open notification settings</string>
<string name="vpnStateEventChannelName">VPN connection alerts</string>
<string name="vpnStateEventChannelDescription">Brief alerts when VPN connects or disconnects</string>
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
</resources>
@@ -1003,6 +1003,11 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
@Suppress("unused")
fun showVpnStateNotification(title: String, message: String) {
ServiceNotification.showVpnStateEvent(applicationContext, title, message)
}
@Suppress("unused")
fun requestNotificationPermission() {
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
@@ -26,6 +26,9 @@ private const val OLD_NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notific
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
const val NOTIFICATION_ID = 1337
const val VPN_STATE_EVENT_NOTIFICATION_ID = 1338
private const val VPN_STATE_EVENT_CHANNEL_ID = "org.amnezia.vpn.vpn_state_events"
private const val GET_ACTIVITY_REQUEST_CODE = 0
private const val CONNECT_REQUEST_CODE = 1
private const val DISCONNECT_REQUEST_CODE = 2
@@ -162,8 +165,42 @@ class ServiceNotification(private val context: Context) {
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
.build()
)
createNotificationChannel(
Builder(VPN_STATE_EVENT_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setShowBadge(false)
.setSound(null, null)
.setVibrationEnabled(false)
.setLightsEnabled(false)
.setName(context.getString(R.string.vpnStateEventChannelName))
.setDescription(context.getString(R.string.vpnStateEventChannelDescription))
.build()
)
}
}
/** Brief alert when VPN connects or disconnects (invoked from Qt via AmneziaActivity). */
fun showVpnStateEvent(context: Context, title: String, message: String) {
if (!context.isNotificationPermissionGranted()) return
val nm = NotificationManagerCompat.from(context)
val notification = NotificationCompat.Builder(context, VPN_STATE_EVENT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_amnezia_round)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setContentIntent(
PendingIntent.getActivity(
context,
GET_ACTIVITY_REQUEST_CODE,
Intent(context, AmneziaActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
.build()
nm.notify(VPN_STATE_EVENT_NOTIFICATION_ID, notification)
}
}
}
+2
View File
@@ -31,6 +31,7 @@ set(LIBS ${LIBS}
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macos_ne_vpn_notification.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
@@ -42,6 +43,7 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macos_ne_vpn_notification.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
+10
View File
@@ -85,6 +85,11 @@ if(NOT ANDROID)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
)
else()
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.h
)
endif()
set(SOURCES ${SOURCES}
@@ -178,6 +183,11 @@ if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
)
else()
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.cpp
)
endif()
set(COMMON_FILES_H
@@ -460,7 +460,6 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
if (apiV2->nameOverriddenByUser) {
newApiV2->name = apiV2->name;
newApiV2->displayName = apiV2->displayName;
newApiV2->nameOverriddenByUser = true;
}
+2 -6
View File
@@ -77,9 +77,7 @@
#include "ui/models/ipSplitTunnelingModel.h"
#include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/utils/notificationHandler.h"
#endif
class NotificationHandler;
class CoreSignalHandlers;
class TestMultipleImports;
@@ -144,9 +142,7 @@ private:
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
NotificationHandler* m_notificationHandler;
#endif
NotificationHandler* m_notificationHandler {};
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
@@ -37,8 +37,8 @@
#include "ui/models/containersModel.h"
#include "core/utils/containerEnum.h"
#include "ui/utils/notificationHandler.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/utils/notificationHandler.h"
#include "ui/utils/systemTrayNotificationHandler.h"
#endif
@@ -410,22 +410,23 @@ void CoreSignalHandlers::initIosSettingsHandler()
void CoreSignalHandlers::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_coreController->m_notificationHandler = NotificationHandler::create(m_coreController);
connect(m_coreController->m_connectionController, &ConnectionController::connectionStateChanged, m_coreController->m_notificationHandler,
&NotificationHandler::setConnectionState);
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
connect(m_coreController->m_notificationHandler, &NotificationHandler::raiseRequested, m_coreController->m_pageController, &PageController::raiseMainWindow);
connect(m_coreController->m_notificationHandler, &NotificationHandler::connectRequested, m_coreController->m_connectionUiController,
static_cast<void (ConnectionUiController::*)()>(&ConnectionUiController::openConnection));
connect(m_coreController->m_notificationHandler, &NotificationHandler::disconnectRequested, m_coreController->m_connectionUiController,
&ConnectionUiController::closeConnection);
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated);
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_coreController->m_notificationHandler);
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
if (auto *trayHandler = qobject_cast<SystemTrayNotificationHandler *>(m_coreController->m_notificationHandler)) {
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
}
#endif
}
void CoreSignalHandlers::initUpdateFoundHandler()
@@ -226,74 +226,37 @@ void InstallController::clearCachedProfile(const QString &serverId, DockerContai
ErrorCode InstallController::validateAndPrepareConfig(const QString &serverId)
{
const auto kind = m_serversRepository->serverKind(serverId);
DockerContainer container = DockerContainer::None;
ContainerConfig containerConfig;
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
containerConfig = cfg->containerConfig(container);
break;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
containerConfig = cfg->containerConfig(container);
break;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
containerConfig = cfg->containerConfig(container);
break;
}
default:
return ErrorCode::InternalError;
}
if (container == DockerContainer::None) {
return ErrorCode::NoInstalledContainersError;
}
if (containerConfig.protocolConfig.hasClientConfig()) {
return ErrorCode::NoError;
}
if (kind != serverConfigUtils::ConfigType::SelfHostedAdmin) {
return ErrorCode::InternalError;
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
DockerContainer container = adminConfig->defaultContainer;
if (container == DockerContainer::None) {
return ErrorCode::NoInstalledContainersError;
}
ContainerConfig containerConfig = adminConfig->containerConfig(container);
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
const QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
const ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverId, clientName);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
adminConfig->updateContainerConfig(container, containerConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
auto isProtocolConfigExists = [](const ContainerConfig &cfg) {
return cfg.protocolConfig.hasClientConfig();
};
if (!isProtocolConfigExists(containerConfig)) {
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverId, clientName);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
adminConfig->updateContainerConfig(container, containerConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return ErrorCode::NoError;
}
@@ -44,7 +44,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return false;
cfg->description = name;
cfg->displayName = name;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
@@ -52,7 +51,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) return false;
cfg->description = name;
cfg->displayName = name;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
@@ -60,7 +58,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) return false;
cfg->description = name;
cfg->displayName = name;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
@@ -70,7 +67,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) return false;
cfg->name = name;
cfg->displayName = name;
cfg->nameOverriddenByUser = true;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
@@ -192,36 +192,6 @@ void SettingsController::clearSettings()
toggleAutoStart(false);
}
bool SettingsController::isFileEncryptionEnabled()
{
return m_appSettingsRepository->isFileEncryption();
}
void SettingsController::toggleFileEncryption(bool enable)
{
m_appSettingsRepository->setFileEncryption(enable);
}
void SettingsController::setPassword(QString pwd)
{
m_appSettingsRepository->setPassword(pwd);
}
QString SettingsController::getPassword()
{
return m_appSettingsRepository->getPassword();
}
void SettingsController::setHint(QString hint)
{
m_appSettingsRepository->setHint(hint);
}
QString SettingsController::getHint()
{
return m_appSettingsRepository->getHint();
}
bool SettingsController::isAutoConnectEnabled() const
{
return m_appSettingsRepository->isAutoConnect();
@@ -44,15 +44,6 @@ public:
void clearSettings();
bool isFileEncryptionEnabled();
void toggleFileEncryption(bool enable);
void setPassword(QString pwd);
QString getPassword();
void setHint(QString hint);
QString getHint();
bool isAutoConnectEnabled() const;
void toggleAutoConnect(bool enable);
+20 -12
View File
@@ -21,13 +21,13 @@ namespace
Logger logger("UpdateController");
#if defined(Q_OS_WINDOWS)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_windows_x64.exe");
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-win64.exe");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos_x64.pkg");
#elif defined(Q_OS_MACOS)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Darwin.pkg");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.run");
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Linux.run");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
#endif
}
@@ -106,7 +106,7 @@ void UpdateController::fetchGatewayUrl()
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
.then(this, [this](QPair<ErrorCode, QByteArray> result) {
auto [err, gatewayResponse] = result;
if (err != ErrorCode::NoError) {
logger.error() << errorString(err);
@@ -184,7 +184,7 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
.arg(operation, reply->errorString(), QString::number(error));
});
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
QStringList errorStrings;
for (const QSslError &err : errors) {
@@ -196,13 +196,21 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
{
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
} else {
QString err = reply->errorString();
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
logger.error() << "Error message:" << err;
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
}
}
QString UpdateController::composeDownloadUrl() const
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
return m_baseUrl + "/" + fileName;
#else
@@ -212,7 +220,7 @@ QString UpdateController::composeDownloadUrl() const
void UpdateController::runInstaller()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (m_downloadUrl.isEmpty()) {
logger.error() << "Download URL is empty";
return;
@@ -244,7 +252,7 @@ void UpdateController::runInstaller()
#if defined(Q_OS_WINDOWS)
runWindowsInstaller(kInstallerLocalPath);
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
#elif defined(Q_OS_MACOS)
runMacInstaller(kInstallerLocalPath);
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
runLinuxInstaller(kInstallerLocalPath);
@@ -284,7 +292,7 @@ int UpdateController::runWindowsInstaller(const QString &installerPath)
}
#endif
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
#if defined(Q_OS_MACOS)
int UpdateController::runMacInstaller(const QString &installerPath)
{
// Create temporary directory for extraction
@@ -300,33 +300,6 @@ void SecureAppSettingsRepository::setStrictKillSwitchEnabled(bool enabled)
setValue("Conf/strictKillSwitchEnabled", enabled);
}
bool SecureAppSettingsRepository::isFileEncryption() const
{
return value("Sec/fileEncryption", false).toBool();
}
void SecureAppSettingsRepository::setFileEncryption(bool enabled)
{
setValue("Sec/fileEncryption", enabled);
}
QString SecureAppSettingsRepository::getPassword() const
{
return value("Sec/password", "").toString();
}
void SecureAppSettingsRepository::setPassword(const QString &pwd)
{
setValue("Sec/password", pwd);
}
QString SecureAppSettingsRepository::getHint() const
{
return value("Sec/hint", "").toString();
}
void SecureAppSettingsRepository::setHint(const QString &hint)
{
setValue("Sec/hint", hint);
}
bool SecureAppSettingsRepository::isAutoConnect() const
{
return value("Conf/autoConnect", false).toBool();
@@ -64,13 +64,6 @@ public:
void setKillSwitchEnabled(bool enabled);
bool isStrictKillSwitchEnabled() const;
void setStrictKillSwitchEnabled(bool enabled);
bool isFileEncryption() const;
void setFileEncryption(bool enabled);
QString getPassword() const;
void setPassword(const QString &pwd);
QString getHint() const;
void setHint(const QString &hint);
bool isAutoConnect() const;
void setAutoConnect(bool enabled);
+20
View File
@@ -2,6 +2,7 @@
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/configKeys.h"
#include <QLatin1Char>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
@@ -103,6 +104,10 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
return amnezia::ErrorCode::ApiUpdateRequestError;
}
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << httpStatusCode;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
@@ -228,3 +233,18 @@ QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
return vpnKeyText;
}
QString apiUtils::countryCodeBaseForFlag(const QString &fullCountryCode)
{
const QString trimmed = fullCountryCode.trimmed();
if (trimmed.isEmpty()) {
return QString();
}
const int dashIdx = trimmed.indexOf(QLatin1Char('-'));
const QString base = dashIdx < 0 ? trimmed : trimmed.left(dashIdx);
const QString normalized = base.trimmed();
if (normalized.isEmpty()) {
return QString();
}
return normalized.toUpper();
}
+3
View File
@@ -25,6 +25,9 @@ namespace apiUtils
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
// ISO2-style segment for flagKit assets (e.g. US-WEST -> US). Do not use in API request bodies.
QString countryCodeBaseForFlag(const QString &fullCountryCode);
}
#endif // APIUTILS_H
-3
View File
@@ -1,3 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 494 B

-3
View File
@@ -1,3 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 592 B

-2
View File
@@ -36,8 +36,6 @@
<file>controls/home.svg</file>
<file>controls/infinity.svg</file>
<file>controls/info.svg</file>
<file>controls/lock-locked.svg</file>
<file>controls/lock-unlocked.svg</file>
<file>controls/mail.svg</file>
<file>controls/map-pin.svg</file>
<file>controls/more-vertical.svg</file>
+2 -6
View File
@@ -1,12 +1,13 @@
#include <QDebug>
#include <QTimer>
#include <libssh/libssh.h>
#include "amneziaApplication.h"
#include "core/utils/osSignalHandler.h"
#include "core/utils/migrations.h"
#include "version.h"
#include <QTimer>
#ifdef Q_OS_WIN
#include "Windows.h"
#endif
@@ -46,11 +47,6 @@ int main(int argc, char *argv[])
AmneziaApplication app(argc, argv);
OsSignalHandler::setup();
ssh_init();
QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
ssh_finalize();
});
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
@@ -307,6 +307,16 @@ void AndroidController::requestNotificationPermission()
callActivityMethod("requestNotificationPermission", "()V");
}
void AndroidController::showVpnStateNotification(const QString &title, const QString &message)
{
if (!isNotificationPermissionGranted()) {
return;
}
callActivityMethod("showVpnStateNotification", "(Ljava/lang/String;Ljava/lang/String;)V",
QJniObject::fromString(title).object<jstring>(),
QJniObject::fromString(message).object<jstring>());
}
bool AndroidController::requestAuthentication()
{
QEventLoop wait;
@@ -53,6 +53,7 @@ public:
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
bool isNotificationPermissionGranted();
void requestNotificationPermission();
void showVpnStateNotification(const QString &title, const QString &message);
bool requestAuthentication();
void sendTouch(float x, float y);
@@ -0,0 +1,19 @@
#include "android_notificationhandler.h"
#include "android_controller.h"
AndroidNotificationHandler::AndroidNotificationHandler(QObject *parent)
: NotificationHandler(parent)
{
}
void AndroidNotificationHandler::notify(Message type, const QString &title, const QString &message, int timerMsec)
{
Q_UNUSED(type);
Q_UNUSED(timerMsec);
// Permission is checked on the Kotlin side as well; avoid JNI if already denied.
if (!AndroidController::instance()->isNotificationPermissionGranted()) {
return;
}
AndroidController::instance()->showVpnStateNotification(title, message);
}
@@ -0,0 +1,16 @@
#ifndef ANDROID_NOTIFICATIONHANDLER_H
#define ANDROID_NOTIFICATIONHANDLER_H
#include "ui/utils/notificationHandler.h"
class AndroidNotificationHandler final : public NotificationHandler {
Q_OBJECT
public:
explicit AndroidNotificationHandler(QObject *parent = nullptr);
protected:
void notify(Message type, const QString &title, const QString &message, int timerMsec) override;
};
#endif // ANDROID_NOTIFICATIONHANDLER_H
+12 -6
View File
@@ -61,6 +61,7 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
Q_UNUSED(timerMsec);
if (!m_delegate) {
return;
@@ -71,11 +72,13 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
content.body = message.toNSString();
content.sound = [UNNotificationSound defaultSound];
int timerSec = timerMsec / 1000;
NSTimeInterval delay = 0.1;
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
NSString* requestId = [NSString stringWithFormat:@"amneziavpn.vpnstate.%lld",
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:requestId
content:content
trigger:trigger];
@@ -143,6 +146,7 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
Q_UNUSED(timerMsec);
if (!m_delegate) {
return;
@@ -153,11 +157,13 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
content.body = message.toNSString();
content.sound = [UNNotificationSound defaultSound];
int timerSec = timerMsec / 1000;
NSTimeInterval delay = 0.1;
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
NSString* requestId = [NSString stringWithFormat:@"amneziavpn.vpnstate.%lld",
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:requestId
content:content
trigger:trigger];
@@ -0,0 +1,8 @@
#ifndef MACOS_NE_VPN_NOTIFICATION_H
#define MACOS_NE_VPN_NOTIFICATION_H
class QString;
void macosNePostVpnStateNotification(const QString &title, const QString &message);
#endif
@@ -0,0 +1,91 @@
#include "macos_ne_vpn_notification.h"
#include <QtGlobal>
#include <QString>
#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>
namespace {
@interface MacosNeVpnNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
@end
@implementation MacosNeVpnNotificationDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
Q_UNUSED(center)
Q_UNUSED(notification)
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
{
Q_UNUSED(center)
Q_UNUSED(response)
completionHandler();
}
@end
MacosNeVpnNotificationDelegate *delegateInstance()
{
static MacosNeVpnNotificationDelegate *d;
static dispatch_once_t once;
dispatch_once(&once, ^{
d = [[MacosNeVpnNotificationDelegate alloc] init];
});
return d;
}
void ensureNotificationCenterSetup()
{
static dispatch_once_t once;
dispatch_once(&once, ^{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError *_Nullable error) {
Q_UNUSED(granted);
if (!error) {
center.delegate = delegateInstance();
}
}];
});
}
} // namespace
void macosNePostVpnStateNotification(const QString &title, const QString &message)
{
ensureNotificationCenterSetup();
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = title.toNSString();
content.body = message.toNSString();
content.sound = nil;
NSTimeInterval delay = 0.1;
UNTimeIntervalNotificationTrigger *trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
NSString *identifier =
[NSString stringWithFormat:@"amneziavpn.vpnstate.%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content
trigger:trigger];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request
withCompletionHandler:^(NSError *_Nullable error) {
if (error) {
NSLog(@"macosNePostVpnStateNotification failed: %@", error);
}
}];
}
+1 -1
View File
@@ -45,7 +45,7 @@ private:
QStringList encryptedKeys; // encode only key listed here
// only this fields need for backup
QStringList m_fieldsToBackup = {
"Conf/", "Servers/", "Sec/",
"Conf/", "Servers/",
};
mutable QByteArray m_key;
@@ -16,6 +16,10 @@
#include <QFutureWatcher>
#include <QTimer>
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#endif
namespace
{
namespace configKey
@@ -117,8 +121,6 @@ bool SubscriptionUiController::exportNativeConfig(const QString &serverId, const
}
const bool saved = SystemController::saveFile(fileName, nativeConfig);
if (m_settingsController->isFileEncryptionEnabled())
SystemController::encryptFile(fileName, m_settingsController->getPassword(), m_settingsController->getHint());
getAccountInfo(serverId, true);
return saved;
}
@@ -37,9 +37,6 @@ namespace PageLoader
PageSettingsSplitTunneling,
PageSettingsAppSplitTunneling,
PageSettingsKillSwitch,
PageSettingsAppEncryption,
PageSettingsAppPassword,
PageSettingsAppPasswordConfirm,
PageSettingsApiServerInfo,
PageSettingsApiAvailableCountries,
PageSettingsApiSupport,
@@ -1,5 +1,6 @@
#include "serversUiController.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
@@ -215,7 +216,11 @@ QString ServersUiController::getDefaultServerImagePathCollapsed() const
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
return "";
}
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
const QString imageCode = apiUtils::countryCodeBaseForFlag(description.apiServerCountryCode.toUpper());
if (imageCode.isEmpty()) {
return QString();
}
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(imageCode);
}
}
return "";
@@ -139,8 +139,6 @@ void SettingsUiController::backupAppConfig(const QString &fileName)
if (!SystemController::saveFile(fileName, data)) {
qInfo() << "SettingsUiController::backupAppConfig: save or share was cancelled or failed";
}
if (isFileEncryptionEnabled())
SystemController::encryptFile(fileName, getPassword(), getHint());
}
void SettingsUiController::restoreAppConfig(const QString &fileName)
@@ -188,57 +186,6 @@ void SettingsUiController::clearSettings()
#endif
}
bool SettingsUiController::isFileEncryptionEnabled()
{
return m_settingsController->isFileEncryptionEnabled();
}
void SettingsUiController::toggleFileEncryption(bool enable)
{
m_settingsController->toggleFileEncryption(enable);
emit fileEncryptionStateChanged();
}
void SettingsUiController::setPassword(QString pwd)
{
m_settingsController->setPassword(pwd);
}
QString SettingsUiController::getPassword()
{
return m_settingsController->getPassword();
}
void SettingsUiController::setHint(QString hint)
{
m_settingsController->setHint(hint);
}
QString SettingsUiController::getHint()
{
return m_settingsController->getHint();
}
void SettingsUiController::setTempPassword(QString pwd)
{
tempPassword = pwd;
}
QString SettingsUiController::getTempPassword()
{
return tempPassword;
}
void SettingsUiController::setTempHint(QString hint)
{
tempHint = hint;
}
QString SettingsUiController::getTempHint()
{
return tempHint;
}
bool SettingsUiController::isAutoConnectEnabled()
{
return m_settingsController->isAutoConnectEnabled();
@@ -61,14 +61,6 @@ public slots:
void clearSettings();
bool isFileEncryptionEnabled();
void toggleFileEncryption(bool enable);
void setPassword(QString pwd);
QString getPassword();
void setHint(QString hint);
QString getHint();
bool isAutoConnectEnabled();
void toggleAutoConnect(bool enable);
@@ -81,11 +73,6 @@ public slots:
bool isNewsNotificationsEnabled();
void toggleNewsNotificationsEnabled(bool enable);
void setTempPassword(QString pwd);
QString getTempPassword();
void setTempHint(QString hint);
QString getTempHint();
bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable);
@@ -150,13 +137,7 @@ signals:
void isHomeAdLabelVisibleChanged(bool visible);
void startMinimizedChanged();
void fileEncryptionStateChanged();
void changingPassword();
private:
QString tempPassword;
QString tempHint;
SettingsController* m_settingsController;
ServersController* m_serversController;
LanguageUiController* m_languageUiController;
-322
View File
@@ -11,21 +11,6 @@
#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
@@ -119,313 +104,6 @@ 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)
{
-13
View File
@@ -15,23 +15,10 @@ 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);
Q_INVOKABLE bool QEncryptFile(const QString &filePath, const QString &password, const QString &hint)
{
return encryptFile(filePath, password, 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();
+2 -1
View File
@@ -5,6 +5,7 @@
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/api/apiUtils.h"
#include "logger.h"
namespace
@@ -41,7 +42,7 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
return countryInfo.countryName;
}
case CountryImageCodeRole: {
return countryInfo.countryCode.toUpper();
return apiUtils::countryCodeBaseForFlag(countryInfo.countryCode);
}
case IsIssuedRole: {
return isIssued;
@@ -1,86 +0,0 @@
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")
}
}
}
}
-130
View File
@@ -1,130 +0,0 @@
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()
}
}
}
}
@@ -199,10 +199,11 @@ Item {
leftImageSource: root.buttonImageSource
anchors.top: content.top
anchors.bottom: content.bottom
anchors.right: content.right
height: root.errorText !== "" ? content.implicitHeight - errorField.height - 5: content.implicitHeight
width: root.errorText !== "" ? content.implicitHeight - errorField.height - 5: content.implicitHeight
height: content.implicitHeight
width: content.implicitHeight
squareLeftSide: true
clickedFunc: function() {
@@ -60,16 +60,6 @@ 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 {
@@ -1,187 +0,0 @@
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()
}
}
}
}
}
}
@@ -1,215 +0,0 @@
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
}
}
@@ -1,155 +0,0 @@
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
}
}
@@ -213,23 +213,6 @@ 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
+1 -21
View File
@@ -67,16 +67,6 @@ 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
@@ -150,20 +140,10 @@ PageType {
var filePath = SystemController.getFileName(qsTr("Open backup file"),
qsTr("Backup files (*.backup)"))
if (filePath !== "") {
passwordDrawer.fileName = filePath
SystemController.isFileEncrypted(filePath) ? passwordDrawer.restoreSecuredBackup() : restoreBackup(filePath)
restoreBackup(filePath)
}
}
}
PasswordDrawer {
id: passwordDrawer
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.45
}
}
}
+6 -6
View File
@@ -48,8 +48,8 @@ PageType {
Layout.rightMargin: 16
headerText: qsTr("Logging")
descriptionText: qsTr("Logs help diagnose app errors and connection issues" +
"Logging is disabled by default. Enable it when troubleshooting or if requested by support")
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.")
}
SwitcherType {
@@ -60,7 +60,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Enable logging")
text: qsTr("Enable logs")
checked: SettingsController.isLoggingEnabled
@@ -77,7 +77,7 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: -8
text: qsTr("Delete all logs")
text: qsTr("Clear logs")
leftImageSource: "qrc:/images/controls/trash.svg"
isSmallLeftImage: true
@@ -154,7 +154,7 @@ PageType {
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Save logs to file")
text: qsTr("Export logs")
leftImageSource: "qrc:/images/controls/save.svg"
isSmallLeftImage: true
@@ -178,7 +178,7 @@ PageType {
QtObject {
id: clientLogs
readonly property string title: qsTr("App logs")
readonly property string title: qsTr("Client logs")
readonly property string description: qsTr("AmneziaVPN logs")
readonly property bool isVisible: true
readonly property var openLogsHandler: function() {
@@ -12,7 +12,6 @@ import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
@@ -265,15 +264,6 @@ PageType {
}
}
PasswordDrawer {
id: passwordDrawer
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.45
}
property list<QtObject> variants: [
amneziaVpn,
selfHostVpn,
@@ -328,12 +318,7 @@ PageType {
qsTr("Backup files (*.backup)"))
if (filePath !== "") {
PageController.showBusyIndicator(true)
if (SystemController.isFileEncrypted(filePath)) {
passwordDrawer.fileName = filePath
passwordDrawer.restoreSecuredBackup()
} else {
SettingsController.restoreAppConfig(filePath)
}
SettingsController.restoreAppConfig(filePath)
PageController.showBusyIndicator(false)
}
}
@@ -351,13 +336,8 @@ PageType {
var nameFilter = "Config files (*.vpn *.ovpn *.conf *.json)"
var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
if (fileName !== "") {
if (SystemController.isFileEncrypted(fileName)) {
passwordDrawer.fileName = fileName
passwordDrawer.importSecuredFile()
} else {
if (ImportController.extractConfigFromFile(fileName)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
if (ImportController.extractConfigFromFile(fileName)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
}
}
-10
View File
@@ -271,16 +271,6 @@ 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
@@ -108,8 +108,6 @@ PageType {
if (fileName !== "") {
PageController.showBusyIndicator(true)
ExportController.exportConfig(fileName)
if (SettingsController.isFileEncryptionEnabled())
SystemController.QEncryptFile(fileName, SettingsController.getPassword(), SettingsController.getHint())
PageController.showBusyIndicator(false)
}
}
@@ -69,16 +69,6 @@ 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"
-5
View File
@@ -7,13 +7,11 @@
<file>Components/HomeContainersListView.qml</file>
<file>Components/HomeSplitTunnelingDrawer.qml</file>
<file>Components/InstalledAppsDrawer.qml</file>
<file>Components/PasswordDrawer.qml</file>
<file>Components/ChangelogDrawer.qml</file>
<file>Components/QuestionDrawer.qml</file>
<file>Components/SelectLanguageDrawer.qml</file>
<file>Components/ServersListView.qml</file>
<file>Components/SettingsContainersListView.qml</file>
<file>Components/EncryptionIndicator.qml</file>
<file>Components/BenefitRow.qml</file>
<file>Components/BenefitsPanel.qml</file>
<file>Components/SubscriptionExpiredDrawer.qml</file>
@@ -101,9 +99,6 @@
<file>Pages2/PageSettingsApiServerInfo.qml</file>
<file>Pages2/PageSettingsApplication.qml</file>
<file>Pages2/PageSettingsAppSplitTunneling.qml</file>
<file>Pages2/PageSettingsAppEncryption.qml</file>
<file>Pages2/PageSettingsAppPassword.qml</file>
<file>Pages2/PageSettingsAppPasswordConfirm.qml</file>
<file>Pages2/PageSettingsBackup.qml</file>
<file>Pages2/PageSettingsConnection.qml</file>
<file>Pages2/PageSettingsDns.qml</file>
+6 -3
View File
@@ -5,16 +5,19 @@
#include <QDebug>
#include "notificationHandler.h"
#if defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID)
# include "platforms/android/android_notificationhandler.h"
#elif defined(Q_OS_IOS)
# include "platforms/ios/iosnotificationhandler.h"
#else
# include "systemTrayNotificationHandler.h"
#endif
// static
NotificationHandler* NotificationHandler::create(QObject* parent) {
#if defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID)
return new AndroidNotificationHandler(parent);
#elif defined(Q_OS_IOS)
return new IOSNotificationHandler(parent);
#else
return new SystemTrayNotificationHandler(parent);
@@ -10,6 +10,10 @@
# include "platforms/macos/macosutils.h"
#endif
#ifdef MACOS_NE
# include "platforms/macos/macos_ne_vpn_notification.h"
#endif
#include <QApplication>
#include <QDesktopServices>
#include <QIcon>
@@ -152,6 +156,12 @@ void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
int timerMsec) {
Q_UNUSED(type);
#ifdef MACOS_NE
Q_UNUSED(timerMsec);
macosNePostVpnStateNotification(title, message);
return;
#endif
QIcon icon(ConnectedTrayIconName);
m_systemTrayIcon.showMessage(title, message, icon, timerMsec);
}
-7
View File
@@ -1,12 +1,5 @@
set(CPACK_PACKAGE_VENDOR AmneziaVPN)
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
if(WIN32)
set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_windows_x64")
elseif(APPLE AND NOT IOS AND NOT MACOS_NE)
set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_macos_x64")
elseif(LINUX AND NOT ANDROID)
set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_linux_x64")
endif()
set(CPACK_PACKAGE_INSTALL_DIRECTORY AmneziaVPN)
set(CPACK_PACKAGE_EXECUTABLES AmneziaVPN AmneziaVPN)
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_CURRENT_LIST_DIR}/sign_binaries.cmake)
-4
View File
@@ -84,10 +84,6 @@ function(detect_os os os_api_level os_sdk os_subsystem os_version)
set(_os_sdk "watch${apple_platform_suffix}")
endif()
endif()
# Macos does not support os.sdk
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(_os_sdk "")
endif()
if(DEFINED os_sdk)
message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}")
set(${os_sdk} ${_os_sdk} PARENT_SCOPE)
+1 -1
View File
@@ -33,7 +33,7 @@ class AmneziaVPN(ConanFile):
if has_ne:
self.requires("awg-apple/2.0.1")
self.requires("hev-socks5-tunnel/2.15.0", options={"as_framework": True})
self.requires("hev-socks5-tunnel/2.14.4", options={"as_framework": True})
self.requires("openvpnadapter/1.0.0")
if os == "Android":
+3 -3
View File
@@ -38,9 +38,9 @@ download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86_64.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.run
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos_x64.pkg
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_windows_x64.exe
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.pkg
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_x64.exe
cd ../
+2 -1
View File
@@ -2,7 +2,7 @@ from conan import ConanFile
from conan.tools.files import get, copy
from conan.tools.layout import basic_layout
from conan.errors import ConanInvalidConfiguration
from conan.tools.env import Environment
from conan.tools.env import VirtualBuildEnv, Environment
import os
import stat
@@ -34,6 +34,7 @@ class AmneziaLibxray(ConanFile):
)
def generate(self):
VirtualBuildEnv(self).generate()
env = Environment()
ndk_path_str = self.conf.get("tools.android:ndk_path")
if ndk_path_str:
+1 -1
View File
@@ -15,7 +15,7 @@ required_conan_version = ">=2.26"
class HevSocks5Tunnel(ConanFile):
name = "hev-socks5-tunnel"
version = "2.15.0"
version = "2.14.4"
settings = "os", "arch", "compiler"
options = {
"shared": [True, False],
-4
View File
@@ -1,4 +0,0 @@
patches:
"2.7.0":
- patch_file: "patches/0001-carefully-handle-CMAKE_GENERATOR_PLATFORM.patch"
- patch_file: "patches/0002-explicitly-pass-unicode-everywhere.patch"
+1 -3
View File
@@ -1,5 +1,5 @@
from conan import ConanFile
from conan.tools.files import get, copy, replace_in_file, apply_conandata_patches, export_conandata_patches
from conan.tools.files import get, copy, replace_in_file
from conan.tools.gnu import Autotools, AutotoolsToolchain, AutotoolsDeps, PkgConfigDeps
from conan.tools.layout import basic_layout
from conan.tools.cmake import cmake_layout, CMakeToolchain, CMake, CMakeDeps
@@ -17,7 +17,6 @@ class Openvpn(ConanFile):
return str(self.settings.os).startswith("Windows")
def export_sources(self):
export_conandata_patches(self)
copy(self, "*applink.c", src=self.recipe_folder, dst=self.export_sources_folder)
def layout(self):
@@ -85,7 +84,6 @@ class Openvpn(ConanFile):
deps.generate()
def build(self):
apply_conandata_patches(self)
if self._is_windows:
cmake = CMake(self)
cmake.configure()
@@ -1,25 +0,0 @@
From 693bee38daaec5962ea3f0939c71e869f202c08a Mon Sep 17 00:00:00 2001
From: Yaroslav Gurov <ygurov@proton.me>
Date: Mon, 18 May 2026 16:58:00 +0200
Subject: [PATCH] carefully handle CMAKE_GENERATOR_PLATFORM
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 198c98ff..7341db70 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -108,7 +108,7 @@ if (MSVC)
"$<$<CONFIG:Release>:/OPT:REF>"
"$<$<CONFIG:Release>:/OPT:ICF>"
)
- if (${CMAKE_GENERATOR_PLATFORM} STREQUAL "x64" OR ${CMAKE_GENERATOR_PLATFORM} STREQUAL "x86")
+ if ("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64" OR "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x86")
add_link_options("$<$<CONFIG:Release>:/CETCOMPAT>")
endif()
else ()
--
2.46.0.windows.1
@@ -1,50 +0,0 @@
From 9a42a0350abaa1a329ad56e40b1900ef78183323 Mon Sep 17 00:00:00 2001
From: Yaroslav Gurov <ygurov@proton.me>
Date: Mon, 18 May 2026 18:05:09 +0200
Subject: [PATCH] explicitly pass unicode everywhere
---
src/openvpnmsica/CMakeLists.txt | 1 +
src/openvpnserv/CMakeLists.txt | 1 +
src/tapctl/CMakeLists.txt | 1 +
3 files changed, 3 insertions(+)
diff --git a/src/openvpnmsica/CMakeLists.txt b/src/openvpnmsica/CMakeLists.txt
index 9126b80f..23f979d6 100644
--- a/src/openvpnmsica/CMakeLists.txt
+++ b/src/openvpnmsica/CMakeLists.txt
@@ -22,6 +22,7 @@ target_sources(openvpnmsica PRIVATE
openvpnmsica_resources.rc
)
target_compile_options(openvpnmsica PRIVATE
+ -DUNICODE
-D_UNICODE
-UNTDDI_VERSION
-D_WIN32_WINNT=_WIN32_WINNT_VISTA
diff --git a/src/openvpnserv/CMakeLists.txt b/src/openvpnserv/CMakeLists.txt
index fc153822..b3a0cff1 100644
--- a/src/openvpnserv/CMakeLists.txt
+++ b/src/openvpnserv/CMakeLists.txt
@@ -19,6 +19,7 @@ function(add_common_options target)
${MC_GEN_DIR}
)
target_compile_options(${target} PRIVATE
+ -DUNICODE
-D_UNICODE
-UNTDDI_VERSION
-D_WIN32_WINNT=_WIN32_WINNT_VISTA
diff --git a/src/tapctl/CMakeLists.txt b/src/tapctl/CMakeLists.txt
index 97702c01..81da46b8 100644
--- a/src/tapctl/CMakeLists.txt
+++ b/src/tapctl/CMakeLists.txt
@@ -19,6 +19,7 @@ target_sources(tapctl PRIVATE
tapctl_resources.rc
)
target_compile_options(tapctl PRIVATE
+ -DUNICODE
-D_UNICODE
-UNTDDI_VERSION
-D_WIN32_WINNT=_WIN32_WINNT_VISTA
--
2.46.0.windows.1