Merge branch 'dev' into fix/custom-server-name-not-saved-after-reloading-config

This commit is contained in:
aiamnezia 2025-06-15 21:25:59 +04:00
commit c50a56e2c3
50 changed files with 1521 additions and 865 deletions

View file

@ -20,6 +20,8 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install Qt'
@ -90,6 +92,8 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Get sources'
@ -156,6 +160,8 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
@ -243,7 +249,7 @@ jobs:
# ------------------------------------------------------
Build-MacOS:
Build-MacOS-old:
runs-on: macos-latest
env:
@ -255,6 +261,78 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
run: |
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
bash deploy/build_macos.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_installer
path: AmneziaVPN.dmg
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-MacOS:
runs-on: macos-latest
env:
QT_VERSION: 6.8.0
QIF_VERSION: 4.8.1
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
@ -324,6 +402,8 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'

View file

@ -20,6 +20,8 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'

6
.gitignore vendored
View file

@ -133,4 +133,8 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
out/
# CMake files
CMakeFiles/
CMakeFiles/
ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.8.6.0
project(${PROJECT} VERSION 4.8.7.2
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@ -11,7 +11,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 2083)
set(APP_ANDROID_VERSION_CODE 2086)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

@ -1 +1 @@
Subproject commit efad1a5b5cb8e8ab61e49ccdca18c9090a0da8d3
Subproject commit 0f3748efd7cc04e0c914304b68931f925bed1259

View file

@ -31,6 +31,9 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
endif()

View file

@ -76,8 +76,22 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
if(DEFINED DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"

View file

@ -120,13 +120,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
if (!m_settings->isSitesSplitTunnelingEnabled()) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
// Prevent ipv6 leak
if (NetworkUtilities::checkIpv6Enabled()) {
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
}
#endif
config.append("block-ipv6\n");
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
@ -135,7 +128,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
#endif
config.append("block-ipv6\n");
}
@ -172,7 +164,6 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
// remove block-outside-dns for all exported configs

View file

@ -22,12 +22,20 @@ namespace apiDefs
namespace key
{
constexpr QLatin1String configVersion("config_version");
constexpr QLatin1String apiEndpoint("api_endpoint");
constexpr QLatin1String apiKey("api_key");
constexpr QLatin1String description("description");
constexpr QLatin1String name("name");
constexpr QLatin1String protocol("protocol");
constexpr QLatin1String apiConfig("api_config");
constexpr QLatin1String stackType("stack_type");
constexpr QLatin1String serviceType("service_type");
constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String vpnKey("vpn_key");
constexpr QLatin1String config("config");
constexpr QLatin1String configs("configs");
constexpr QLatin1String installationUuid("installation_uuid");
constexpr QLatin1String workerLastUpdated("worker_last_updated");
@ -51,6 +59,10 @@ namespace apiDefs
constexpr QLatin1String website("website");
constexpr QLatin1String websiteName("website_name");
constexpr QLatin1String telegram("telegram");
constexpr QLatin1String id("id");
constexpr QLatin1String orderId("order_id");
constexpr QLatin1String migrationCode("migration_code");
}
const int requestTimeoutMsecs = 12 * 1000; // 12 secs

View file

@ -3,6 +3,24 @@
#include <QDateTime>
#include <QJsonObject>
namespace
{
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
QString escapeUnicode(const QString &input)
{
QString output;
for (QChar c : input) {
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
} else {
output += c;
}
}
return output;
}
}
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTime();
@ -23,13 +41,21 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
{
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
switch (configVersion) {
case apiDefs::ConfigSource::Telegram: {
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
if (apiEndpoint.contains(premiumV1Endpoint)) {
return apiDefs::ConfigType::AmneziaPremiumV1;
} else if (apiEndpoint.contains(freeV2Endpoint)) {
return apiDefs::ConfigType::AmneziaFreeV2;
}
};
case apiDefs::ConfigSource::AmneziaGateway: {
constexpr QLatin1String stackPremium("prem");
constexpr QLatin1String stackFree("free");
constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium");
@ -70,6 +96,9 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << reply->error();
return amnezia::ErrorCode::ApiConfigTimeoutError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << reply->error();
return amnezia::ErrorCode::ApiUpdateRequestError;
} else {
QString err = reply->errorString();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -95,3 +124,41 @@ bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
apiDefs::ConfigType::ExternalPremium };
return premiumTypes.contains(getConfigType(serverConfigObject));
}
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
return {};
}
QList<QPair<QString, QVariant>> orderedFields;
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
QString vpnKeyStr = "{";
for (int i = 0; i < orderedFields.size(); ++i) {
const auto &pair = orderedFields[i];
if (pair.second.typeId() == QMetaType::Type::QString) {
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
}
if (i < orderedFields.size() - 1) {
vpnKeyStr += ", ";
}
}
vpnKeyStr += "}";
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
vpnKeyCompressed = vpnKeyCompressed.mid(4);
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
}

View file

@ -19,6 +19,8 @@ namespace apiUtils
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
}
#endif // APIUTILS_H

View file

@ -148,6 +148,9 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
}
void CoreController::initAndroidController()
@ -220,6 +223,8 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler();
}
@ -363,10 +368,29 @@ void CoreController::initPrepareConfigHandler()
});
}
void CoreController::initImportPremiumV2VpnKeyHandler()
{
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
m_importController->extractConfigFromData(vpnKey);
m_importController->importConfig();
emit m_apiPremV1MigrationController->migrationFinished();
});
}
void CoreController::initShowMigrationDrawerHandler()
{
QTimer::singleShot(1000, this, [this]() {
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
m_apiPremV1MigrationController->showMigrationDrawer();
}
});
}
void CoreController::initStrictKillSwitchHandler()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged,
m_vpnConnection.get(), &VpnConnection::onKillSwitchModeChanged);
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
&VpnConnection::onKillSwitchModeChanged);
}
QSharedPointer<PageController> CoreController::pageController() const

View file

@ -7,6 +7,7 @@
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
#include "ui/controllers/connectionController.h"
@ -82,6 +83,8 @@ private:
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
@ -109,6 +112,7 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;

View file

@ -14,8 +14,8 @@
#include "amnezia_application.h"
#include "core/api/apiUtils.h"
#include "utilities.h"
#include "core/networkUtilities.h"
#include "utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/ipcclient.h"
@ -36,10 +36,17 @@ namespace
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
constexpr QLatin1String errorResponsePattern3("Account not found.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
}
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs)
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent)
: QObject(parent),
m_gatewayEndpoint(gatewayEndpoint),
m_isDevEnvironment(isDevEnvironment),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
{
}
@ -58,11 +65,11 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
{
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip});
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
@ -120,11 +127,11 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
{
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip});
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
@ -306,6 +313,13 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
qDebug() << reply->error();
return true;
}
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << reply->error();
return true;
}
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
qDebug() << reply->error();
return true;

View file

@ -15,7 +15,8 @@ class GatewayController : public QObject
Q_OBJECT
public:
explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr);
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
@ -30,6 +31,7 @@ private:
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
};
#endif // GATEWAYCONTROLLER_H

View file

@ -138,7 +138,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
@ -146,7 +146,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return e;
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
@ -154,7 +154,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return e;
e = runScript(credentials,
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
@ -177,7 +177,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@ -383,6 +383,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true;
}
if (container == DockerContainer::Xray) {
if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) {
return true;
}
}
return false;
}

View file

@ -117,6 +117,8 @@ namespace amnezia
ApiServicesMissingError = 1107,
ApiConfigLimitError = 1108,
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
// QFile errors
OpenError = 1200,

View file

@ -74,6 +74,8 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;

View file

@ -149,8 +149,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
// set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
logger.debug() << "Routing configuration failed for"
<< logger.sensitive(ip.toString());
logger.debug() << "Routing configuration failed for" << ip.toString();
return false;
}
}

View file

@ -26,10 +26,22 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
if(DEPLOY)
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"

View file

@ -97,7 +97,7 @@ bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
// Set ifr to interface
int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
if (ret) {
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
logger.error() << "Failed to set IPv4: " << deviceAddr
<< "error:" << strerror(errno);
return false;
}
@ -138,7 +138,7 @@ bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
// Set ifr6 to the interface
ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
if (ret && (errno != EEXIST)) {
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
logger.error() << "Failed to set IPv6: " << deviceAddr
<< "error:" << strerror(errno);
return false;
}

View file

@ -122,7 +122,7 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
// Set ifr to interface
int ret = ioctl(sockfd, SIOCAIFADDR, &ifr);
if (ret) {
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
logger.error() << "Failed to set IPv4: " << deviceAddr
<< "error:" << strerror(errno);
return false;
}
@ -162,7 +162,7 @@ bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
// Set ifr to interface
int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6);
if (ret) {
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
logger.error() << "Failed to set IPv6: " << deviceAddr
<< "error:" << strerror(errno);
return false;
}

View file

@ -144,7 +144,7 @@ void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
for (const IPAddress& prefix : m_exclusionRoutes) {
if (prefix.address().protocol() == protocol) {
logger.debug() << "Removing exclusion route to"
<< logger.sensitive(prefix.toString());
<< prefix.toString();
rtmSendRoute(RTM_DELETE, prefix, rtm->rtm_index, nullptr);
}
}
@ -259,7 +259,7 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
for (const IPAddress& prefix : m_exclusionRoutes) {
if (prefix.address().protocol() == protocol) {
logger.debug() << "Updating exclusion route to"
<< logger.sensitive(prefix.toString());
<< prefix.toString();
rtmSendRoute(rtm_type, prefix, ifindex, addrlist[1].constData());
}
}
@ -510,8 +510,7 @@ bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) {
}
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Adding exclusion route for"
<< logger.sensitive(prefix.toString());
logger.debug() << "Adding exclusion route for" << prefix.toString();
if (m_exclusionRoutes.contains(prefix)) {
logger.warning() << "Exclusion route already exists";
@ -536,8 +535,7 @@ bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
}
bool MacosRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Deleting exclusion route for"
<< logger.sensitive(prefix.toString());
logger.debug() << "Deleting exclusion route for" << prefix.toString();
m_exclusionRoutes.removeAll(prefix);
if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {

View file

@ -303,8 +303,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
data->Age++;
continue;
}
logger.debug() << "Capturing route to"
<< logger.sensitive(prefix.toString());
logger.debug() << "Capturing route to" << prefix.toString();
// Clone the route and direct it into the VPN tunnel.
data = new MIB_IPFORWARD_ROW2;
@ -354,8 +353,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
continue;
}
logger.debug() << "Removing route capture for"
<< logger.sensitive(i.key().toString());
logger.debug() << "Removing route capture for" << i.key().toString();
// Otherwise, this route is no longer in use.
DWORD result = DeleteIpForwardEntry2(data);
@ -368,8 +366,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
}
bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Adding exclusion route for"
<< logger.sensitive(prefix.toString());
logger.debug() << "Adding exclusion route for" << prefix.toString();
// Silently ignore non-routeable addresses.
QHostAddress addr = prefix.address();
@ -437,7 +434,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Deleting exclusion route for"
<< logger.sensitive(prefix.address().toString());
<< prefix.address().toString();
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
if (data == nullptr) {
@ -447,7 +444,7 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
DWORD result = DeleteIpForwardEntry2(data);
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
logger.error() << "Failed to delete route to"
<< logger.sensitive(prefix.toString())
<< prefix.toString()
<< "result:" << result;
}
@ -465,7 +462,7 @@ void WindowsRouteMonitor::flushRouteTable(
DWORD result = DeleteIpForwardEntry2(data);
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
logger.error() << "Failed to delete route to"
<< logger.sensitive(i.key().toString())
<< i.key().toString()
<< "result:" << result;
}
delete data;

View file

@ -236,6 +236,9 @@
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
<file>images/controls/monitor.svg</file>
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
</qresource>
<qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file>

View file

@ -559,6 +559,16 @@ void Settings::disableHomeAdLabel()
setValue("Conf/homeAdLabelVisible", false);
}
bool Settings::isPremV1MigrationReminderActive()
{
return value("Conf/premV1MigrationReminderActive", true).toBool();
}
void Settings::disablePremV1MigrationReminder()
{
setValue("Conf/premV1MigrationReminderActive", false);
}
QStringList Settings::allowedDnsServers() const
{
return value("Conf/allowedDnsServers").toStringList();

View file

@ -174,11 +174,12 @@ public:
QLocale getAppLanguage()
{
return value("Conf/appLanguage", QLocale()).toLocale();
QString localeStr = m_settings.value("Conf/appLanguage").toString();
return QLocale(localeStr);
};
void setAppLanguage(QLocale locale)
{
setValue("Conf/appLanguage", locale);
setValue("Conf/appLanguage", locale.name());
};
bool isScreenshotsEnabled() const
@ -229,6 +230,9 @@ public:
bool isHomeAdLabelVisible();
void disableHomeAdLabel();
bool isPremV1MigrationReminderActive();
void disablePremV1MigrationReminder();
QStringList allowedDnsServers() const;
void setAllowedDnsServers(const QStringList &servers);

File diff suppressed because it is too large Load diff

View file

@ -1,34 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
TextArea {
id: root
width: parent.width
topPadding: 16
leftPadding: 16
color: "#D7D8DB"
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
placeholderTextColor: "#878B91"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
wrapMode: Text.Wrap
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: contextMenu.open()
}
ContextMenuType {
id: contextMenu
textObj: textField
}
}

View file

@ -63,7 +63,8 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
return false;
}
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
@ -76,6 +77,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
apiPayload[configKey::serverCountryCode] = serverCountryCode;
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody);
@ -94,7 +96,8 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
@ -107,6 +110,7 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
apiPayload[configKey::serverCountryCode] = serverCountryCode;
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody);
@ -140,7 +144,8 @@ void ApiConfigsController::copyVpnKeyToClipboard()
bool ApiConfigsController::fillAvailableServices()
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType();
@ -171,7 +176,8 @@ bool ApiConfigsController::importServiceFromGateway()
return false;
}
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto installationUuid = m_settings->getInstallationUuid(true);
auto userCountryCode = m_apiServicesModel->getCountryCode();
@ -184,6 +190,7 @@ bool ApiConfigsController::importServiceFromGateway()
apiPayload[configKey::userCountryCode] = userCountryCode;
apiPayload[configKey::serviceType] = serviceType;
apiPayload[configKey::uuid] = installationUuid;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody);
@ -211,7 +218,8 @@ bool ApiConfigsController::importServiceFromGateway()
bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
@ -228,6 +236,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
apiPayload[configKey::userCountryCode] = userCountryCode;
apiPayload[configKey::serviceType] = serviceType;
apiPayload[configKey::uuid] = installationUuid;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
if (!newCountryCode.isEmpty()) {
apiPayload[configKey::serverCountryCode] = newCountryCode;
@ -278,7 +287,8 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
QThread::msleep(10);
#endif
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto installationUuid = m_settings->getInstallationUuid(true);
@ -308,7 +318,8 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
bool ApiConfigsController::deactivateDevice()
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
@ -327,6 +338,7 @@ bool ApiConfigsController::deactivateDevice()
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true);
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody);
@ -343,7 +355,8 @@ bool ApiConfigsController::deactivateDevice()
bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
@ -362,6 +375,7 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
apiPayload[configKey::uuid] = uuid;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody);

View file

@ -0,0 +1,133 @@
#include "apiPremV1MigrationController.h"
#include <QEventLoop>
#include <QTimer>
#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/controllers/gatewayController.h"
ApiPremV1MigrationController::ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
}
bool ApiPremV1MigrationController::hasConfigsToMigration()
{
QJsonArray vpnKeys;
auto serversCount = m_serversModel->getServersCount();
for (size_t i = 0; i < serversCount; i++) {
auto serverConfigObject = m_serversModel->getServerConfig(i);
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
continue;
}
QString vpnKey = apiUtils::getPremiumV1VpnKey(serverConfigObject);
vpnKeys.append(vpnKey);
}
if (!vpnKeys.isEmpty()) {
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload["configs"] = vpnKeys;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody);
auto migrationsStatus = QJsonDocument::fromJson(responseBody).object();
for (const auto &migrationStatus : migrationsStatus) {
if (migrationStatus == "not_found") {
return true;
}
}
}
return false;
}
void ApiPremV1MigrationController::getSubscriptionList(const QString &email)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/subscription-list"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_email = email;
m_subscriptionsModel = QJsonDocument::fromJson(responseBody).array();
if (m_subscriptionsModel.isEmpty()) {
emit noSubscriptionToMigrate();
return;
}
emit subscriptionsModelChanged();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
QJsonArray ApiPremV1MigrationController::getSubscriptionModel()
{
return m_subscriptionsModel;
}
void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex)
{
QEventLoop wait;
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
wait.exec();
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migration-code"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_subscriptionIndex = subscriptionIndex;
emit otpSuccessfullySent();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
void ApiPremV1MigrationController::migrate(const QString &migrationCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
apiPayload[apiDefs::key::orderId] = m_subscriptionsModel.at(m_subscriptionIndex).toObject().value(apiDefs::key::id).toString();
apiPayload[apiDefs::key::migrationCode] = migrationCode;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migrate"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
auto responseObject = QJsonDocument::fromJson(responseBody).object();
QString premiumV2VpnKey = responseObject.value(apiDefs::key::config).toString();
emit importPremiumV2VpnKey(premiumV2VpnKey);
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
bool ApiPremV1MigrationController::isPremV1MigrationReminderActive()
{
return m_settings->isPremV1MigrationReminderActive();
}
void ApiPremV1MigrationController::disablePremV1MigrationReminder()
{
m_settings->disablePremV1MigrationReminder();
}

View file

@ -0,0 +1,50 @@
#ifndef APIPREMV1MIGRATIONCONTROLLER_H
#define APIPREMV1MIGRATIONCONTROLLER_H
#include <QObject>
#include "ui/models/servers_model.h"
class ApiPremV1MigrationController : public QObject
{
Q_OBJECT
public:
ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
Q_PROPERTY(QJsonArray subscriptionsModel READ getSubscriptionModel NOTIFY subscriptionsModelChanged)
public slots:
bool hasConfigsToMigration();
void getSubscriptionList(const QString &email);
QJsonArray getSubscriptionModel();
void sendMigrationCode(const int subscriptionIndex);
void migrate(const QString &migrationCode);
bool isPremV1MigrationReminderActive();
void disablePremV1MigrationReminder();
signals:
void subscriptionsModelChanged();
void otpSuccessfullySent();
void importPremiumV2VpnKey(const QString &vpnKey);
void errorOccurred(ErrorCode errorCode);
void showMigrationDrawer();
void migrationFinished();
void noSubscriptionToMigrate();
private:
QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
QJsonArray m_subscriptionsModel;
int m_subscriptionIndex;
QString m_email;
};
#endif // APIPREMV1MIGRATIONCONTROLLER_H

View file

@ -5,6 +5,7 @@
#include "core/api/apiUtils.h"
#include "core/controllers/gatewayController.h"
#include "version.h"
namespace
{
@ -48,7 +49,8 @@ bool ApiSettingsController::getAccountInfo(bool reload)
wait.exec();
}
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto processedIndex = m_serversModel->getProcessedServerIndex();
auto serverConfig = m_serversModel->getServerConfig(processedIndex);
@ -59,15 +61,14 @@ bool ApiSettingsController::getAccountInfo(bool reload)
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
apiPayload[configKey::authData] = authData;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
QByteArray responseBody;
if (apiUtils::isPremiumServer(serverConfig)) {
ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return false;
}
ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return false;
}
QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object();

View file

@ -145,7 +145,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName)
}
QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : lines) {
for (const QString &line : std::as_const(lines)) {
m_config.append(line + "\n");
}
@ -163,7 +163,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
}
QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : lines) {
for (const QString &line : std::as_const(lines)) {
m_config.append(line + "\n");
}
@ -183,7 +183,7 @@ void ExportController::generateAwgConfig(const QString &clientName)
}
QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : lines) {
for (const QString &line : std::as_const(lines)) {
m_config.append(line + "\n");
}
@ -211,7 +211,7 @@ void ExportController::generateShadowSocksConfig()
}
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : lines) {
for (const QString &line : std::as_const(lines)) {
m_config.append(line + "\n");
}
@ -240,7 +240,7 @@ void ExportController::generateCloakConfig()
nativeConfig.insert("ProxyMethod", "shadowsocks");
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : lines) {
for (const QString &line : std::as_const(lines)) {
m_config.append(line + "\n");
}
@ -257,7 +257,7 @@ void ExportController::generateXrayConfig(const QString &clientName)
}
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : lines) {
for (const QString &line : std::as_const(lines)) {
m_config.append(line + "\n");
}

View file

@ -126,7 +126,13 @@ void SettingsController::clearLogs()
void SettingsController::backupAppConfig(const QString &fileName)
{
SystemController::saveFile(fileName, m_settings->backupAppConfig());
QByteArray data = m_settings->backupAppConfig();
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject config = doc.object();
config["Conf/autoStart"] = Autostart::isAutostart();
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
}
void SettingsController::restoreAppConfig(const QString &fileName)
@ -140,9 +146,30 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
{
bool ok = m_settings->restoreAppConfig(data);
if (ok) {
QJsonObject newConfigData = QJsonDocument::fromJson(data).object();
#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) || defined(Q_OS_MACX)
bool autoStart = false;
if (newConfigData.contains("Conf/autoStart")) {
autoStart = newConfigData["Conf/autoStart"].toBool();
}
toggleAutoStart(autoStart);
#endif
m_serversModel->resetModel();
m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toBool();
m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode);
m_appSplitTunnelingModel->toggleSplitTunneling(appSplittunnelingEnabled);
#endif
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toBool();
m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode);
m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled);
emit restoreBackupFinished();
} else {
emit changeSettingsErrorOccurred(tr("Backup file is corrupted"));
@ -167,6 +194,8 @@ void SettingsController::clearSettings()
m_appSplitTunnelingModel->setRouteMode(Settings::AppsRouteMode::VpnAllExceptApps);
m_appSplitTunnelingModel->toggleSplitTunneling(false);
toggleAutoStart(false);
emit changeSettingsFinished(tr("All settings have been reset to default values"));
#ifdef Q_OS_IOS

View file

@ -20,6 +20,7 @@ bool XrayConfigModel::setData(const QModelIndex &index, const QVariant &value, i
switch (role) {
case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break;
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
}
emit dataChanged(index, index, QList { role });
@ -34,6 +35,7 @@ QVariant XrayConfigModel::data(const QModelIndex &index, int role) const
switch (role) {
case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite);
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::xray::defaultPort);
}
return QVariant();
@ -67,6 +69,7 @@ QHash<int, QByteArray> XrayConfigModel::roleNames() const
QHash<int, QByteArray> roles;
roles[SiteRole] = "site";
roles[PortRole] = "port";
return roles;
}

View file

@ -12,7 +12,8 @@ class XrayConfigModel : public QAbstractListModel
public:
enum Roles {
SiteRole
SiteRole,
PortRole
};
explicit XrayConfigModel(QObject *parent = nullptr);

View file

@ -349,6 +349,25 @@ void ServersModel::removeServer()
endResetModel();
}
void ServersModel::removeServer(const int serverIndex)
{
beginResetModel();
m_settings->removeServer(serverIndex);
m_servers = m_settings->serversArray();
if (m_settings->defaultServerIndex() == serverIndex) {
setDefaultServerIndex(0);
} else if (m_settings->defaultServerIndex() > serverIndex) {
setDefaultServerIndex(m_settings->defaultServerIndex() - 1);
}
if (m_settings->serversCount() == 0) {
setDefaultServerIndex(-1);
}
setProcessedServerIndex(m_defaultServerIndex);
endResetModel();
}
QHash<int, QByteArray> ServersModel::roleNames() const
{
QHash<int, QByteArray> roles;

View file

@ -90,6 +90,7 @@ public slots:
void addServer(const QJsonObject &server);
void editServer(const QJsonObject &server, const int serverIndex);
void removeServer();
void removeServer(const int serverIndex);
QJsonObject getServerConfig(const int serverIndex);

View file

@ -0,0 +1,194 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtCore
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
DrawerType2 {
id: root
expandedHeight: parent.height * 0.9
Connections {
target: ApiPremV1MigrationController
function onErrorOccurred(error, goToPageHome) {
PageController.showErrorMessage(error)
root.closeTriggered()
}
}
expandedStateContent: Item {
implicitHeight: root.expandedHeight
ListViewType {
id: listView
anchors.fill: parent
model: 1 // fake model to force the ListView to be created without a model
snapMode: ListView.NoSnap
header: ColumnLayout {
width: listView.width
Header2Type {
id: header
Layout.fillWidth: true
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Switch to the new Amnezia Premium subscription")
}
}
delegate: ColumnLayout {
width: listView.width
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
horizontalAlignment: Text.AlignLeft
textFormat: Text.RichText
text: {
var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ")
str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:")
str += "<ul style='margin-left: -16px;'>"
str += qsTr("<li>13 locations (with more coming soon)</li>")
str += qsTr("<li>Easier switching between countries in the app</li>")
str += qsTr("<li>Personal dashboard to manage your subscription</li>")
str += "</ul>"
str += qsTr("Old keys will be deactivated after switching.")
}
}
TextFieldWithHeaderType {
id: emailLabel
Layout.fillWidth: true
borderColor: AmneziaStyle.color.mutedGray
headerTextColor: AmneziaStyle.color.paleGray
headerText: qsTr("Email")
textField.placeholderText: qsTr("mail@example.com")
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
Connections {
target: ApiPremV1MigrationController
function onNoSubscriptionToMigrate() {
emailLabel.errorText = qsTr("No old format subscriptions for a given email")
}
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.topMargin: 16
color: AmneziaStyle.color.mutedGray
text: qsTr("Enter the email you used for your current subscription")
}
ApiPremV1SubListDrawer {
id: apiPremV1SubListDrawer
parent: root
anchors.fill: parent
}
OtpCodeDrawer {
id: otpCodeDrawer
parent: root
anchors.fill: parent
}
BasicButtonType {
id: yesButton
Layout.fillWidth: true
Layout.topMargin: 32
text: qsTr("Continue")
clickedFunc: function() {
PageController.showBusyIndicator(true)
ApiPremV1MigrationController.getSubscriptionList(emailLabel.textField.text)
PageController.showBusyIndicator(false)
}
}
BasicButtonType {
id: noButton
Layout.fillWidth: true
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Remind me later")
clickedFunc: function() {
root.closeTriggered()
}
}
BasicButtonType {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 32
Layout.bottomMargin: 32
implicitHeight: 32
defaultColor: "transparent"
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Don't remind me again")
clickedFunc: function() {
var headerText = qsTr("No more reminders? You can always switch to the new format in the server settings")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
ApiPremV1MigrationController.disablePremV1MigrationReminder()
root.closeTriggered()
}
var noButtonFunction = function() {
}
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
}
}
}
}

View file

@ -0,0 +1,89 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
Connections {
target: ApiPremV1MigrationController
function onSubscriptionsModelChanged() {
if (ApiPremV1MigrationController.subscriptionsModel.length > 1) {
root.openTriggered()
} else {
sendMigrationCode(0)
}
}
}
function sendMigrationCode(index) {
PageController.showBusyIndicator(true)
ApiPremV1MigrationController.sendMigrationCode(index)
root.closeTriggered()
PageController.showBusyIndicator(false)
}
expandedHeight: parent.height * 0.9
expandedStateContent: Item {
implicitHeight: root.expandedHeight
ListViewType {
id: listView
anchors.fill: parent
model: ApiPremV1MigrationController.subscriptionsModel
header: ColumnLayout {
width: listView.width
Header2Type {
id: header
Layout.fillWidth: true
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Choose Subscription")
}
}
delegate: Item {
implicitWidth: listView.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
LabelWithButtonType {
id: server
Layout.fillWidth: true
text: qsTr("Order ID: ") + modelData.id
descriptionText: qsTr("Purchase Date: ") + Qt.formatDateTime(new Date(modelData.created_at), "dd.MM.yyyy hh:mm")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
sendMigrationCode(index)
}
}
DividerType {}
}
}
}
}
}

View file

@ -0,0 +1,77 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
Connections {
target: ApiPremV1MigrationController
function onOtpSuccessfullySent() {
root.openTriggered()
}
}
expandedHeight: parent.height * 0.6
expandedStateContent: Item {
implicitHeight: root.expandedHeight
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 0
Header2Type {
id: header
Layout.fillWidth: true
Layout.topMargin: 20
headerText: qsTr("OTP code was sent to your email")
}
TextFieldWithHeaderType {
id: otpFiled
borderColor: AmneziaStyle.color.mutedGray
headerTextColor: AmneziaStyle.color.paleGray
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("OTP Code")
textField.maximumLength: 30
checkEmptyText: true
}
BasicButtonType {
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Continue")
clickedFunc: function() {
PageController.showBusyIndicator(true)
ApiPremV1MigrationController.migrate(otpFiled.textField.text)
PageController.showBusyIndicator(false)
root.closeTriggered()
}
}
}
}
}

View file

@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@ -39,7 +41,7 @@ DrawerType2 {
Layout.rightMargin: 16
Layout.leftMargin: 16
text: headerText
text: root.headerText
}
ParagraphTextType {
@ -48,7 +50,7 @@ DrawerType2 {
Layout.rightMargin: 16
Layout.leftMargin: 16
text: descriptionText
text: root.descriptionText
}
BasicButtonType {
@ -58,11 +60,11 @@ DrawerType2 {
Layout.rightMargin: 16
Layout.leftMargin: 16
text: yesButtonText
text: root.yesButtonText
clickedFunc: function() {
if (yesButtonFunction && typeof yesButtonFunction === "function") {
yesButtonFunction()
if (root.yesButtonFunction && typeof root.yesButtonFunction === "function") {
root.yesButtonFunction()
}
}
}
@ -80,11 +82,13 @@ DrawerType2 {
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: noButtonText
visible: root.noButtonText !== ""
text: root.noButtonText
clickedFunc: function() {
if (noButtonFunction && typeof noButtonFunction === "function") {
noButtonFunction()
if (root.noButtonFunction && typeof root.noButtonFunction === "function") {
root.noButtonFunction()
}
}
}

View file

@ -33,6 +33,31 @@ PageType {
}
}
Connections {
target: ApiPremV1MigrationController
function onMigrationFinished() {
apiPremV1MigrationDrawer.closeTriggered()
var headerText = qsTr("You've successfully switched to the new Amnezia Premium subscription!")
var descriptionText = qsTr("Old keys will no longer work. Please use your new subscription key to connect. \nThank you for staying with us!")
var yesButtonText = qsTr("Continue")
var noButtonText = ""
var yesButtonFunction = function() {
}
var noButtonFunction = function() {
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
function onShowMigrationDrawer() {
apiPremV1MigrationDrawer.openTriggered()
}
}
Item {
objectName: "homeColumnItem"
@ -429,4 +454,9 @@ PageType {
}
}
}
ApiPremV1MigrationDrawer {
id: apiPremV1MigrationDrawer
anchors.fill: parent
}
}

View file

@ -103,8 +103,29 @@ PageType {
}
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
}
}
checkEmptyText: true
}
BasicButtonType {
id: basicButton
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24

View file

@ -88,6 +88,7 @@ PageType {
LabelWithButtonType {
Layout.fillWidth: true
visible: link !== ""
text: title
descriptionText: description
rightImageSource: "qrc:/images/controls/external-link.svg"

View file

@ -49,7 +49,7 @@ PageType {
if (!ConnectionController.isConnected) {
SettingsController.isKillSwitchEnabled = checked
} else {
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection"))
PageController.showNotificationMessage(qsTr("KillSwitch settings cannot be changed during an active connection"))
switcher.checked = SettingsController.isKillSwitchEnabled
}
}
@ -66,7 +66,7 @@ PageType {
checked: !SettingsController.strictKillSwitchEnabled
text: qsTr("Soft KillSwitch")
descriptionText: qsTr("Internet connection is blocked if VPN connection drops accidentally")
descriptionText: qsTr("Internet access is blocked if the VPN disconnects unexpectedly")
onClicked: function() {
SettingsController.strictKillSwitchEnabled = false
@ -81,15 +81,16 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected
visible: false
enabled: false //SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected
checked: SettingsController.strictKillSwitchEnabled
text: qsTr("Strict KillSwitch")
descriptionText: qsTr("Internet connection is blocked even if VPN was turned off manually or not started")
descriptionText: qsTr("Internet connection is blocked even when VPN is turned off manually or hasn't started")
onClicked: function() {
var headerText = qsTr("Just a little heads-up")
var descriptionText = qsTr("If you disconnect from VPN or the VPN connection drops while the Strict Kill Switch is turned on, your internet access will be disabled. To restore it, connect to VPN, change the Kill Switch mode or turn the Kill Switch off.")
var descriptionText = qsTr("If the VPN disconnects or drops while Strict KillSwitch is enabled, internet access will be blocked. To restore access, reconnect VPN or disable/change the KillSwitch.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
@ -103,7 +104,9 @@ PageType {
}
}
DividerType {}
DividerType {
visible: false
}
LabelWithButtonType {
Layout.topMargin: 32
@ -111,7 +114,7 @@ PageType {
enabled: true
text: qsTr("DNS Exceptions")
descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered")
descriptionText: qsTr("DNS servers listed here will remain accessible when KillSwitch is active.")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {

View file

@ -43,7 +43,7 @@ PageType {
Layout.leftMargin: 16
headerText: qsTr("DNS Exceptions")
descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered")
descriptionText: qsTr("DNS servers listed here will remain accessible when KillSwitch is active")
}
}

View file

@ -257,6 +257,24 @@ PageType {
DividerType {
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
}
LabelWithButtonType {
id: labelWithButton6
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
Layout.fillWidth: true
text: qsTr("Switch to the new Amnezia Premium subscription")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function() {
PageController.goToPageHome()
ApiPremV1MigrationController.showMigrationDrawer()
}
}
DividerType {
visible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
}
}
}
}

View file

@ -28,10 +28,10 @@ wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSIO
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_armeabi-v7a.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86_64.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux.tar.zip
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar.zip
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.dmg
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos_old.dmg
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_x64.exe
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_windows_x64.exe
cd ../