mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
Compare commits
25 Commits
feature/ki
...
bugfix/sha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1426d60e78 | ||
|
|
49bf5d98ff | ||
|
|
369e08844f | ||
|
|
48a5452a65 | ||
|
|
c2f9340db6 | ||
|
|
a6508e642a | ||
|
|
a3e73797c2 | ||
|
|
df7bf204ea | ||
|
|
e16243ff55 | ||
|
|
e23cbe67ad | ||
|
|
7702f2f74c | ||
|
|
b457ef9a3f | ||
|
|
a28ed6a977 | ||
|
|
0c73682cfc | ||
|
|
7e380b6cfb | ||
|
|
63b5257986 | ||
|
|
acc4485e81 | ||
|
|
2c44999a31 | ||
|
|
e59a48f9f4 | ||
|
|
b86356b0cc | ||
|
|
f6d7552b58 | ||
|
|
5bd88ac2e9 | ||
|
|
94fa5b59f3 | ||
|
|
7169480999 | ||
|
|
c44ce0d77c |
82
.github/workflows/deploy.yml
vendored
82
.github/workflows/deploy.yml
vendored
@@ -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'
|
||||
|
||||
2
.github/workflows/tag-deploy.yml
vendored
2
.github/workflows/tag-deploy.yml
vendored
@@ -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
6
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Submodule client/3rd-prebuilt updated: efad1a5b5c...0f3748efd7
@@ -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()
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "core/networkUtilities.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
@@ -119,20 +120,14 @@ 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
|
||||
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) {
|
||||
|
||||
// no redirect-gateway
|
||||
// no redirect-gateway
|
||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#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");
|
||||
}
|
||||
@@ -169,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
|
||||
|
||||
@@ -140,98 +140,83 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
{
|
||||
return {
|
||||
{ DockerContainer::OpenVpn,
|
||||
QObject::tr(
|
||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
||||
"It employs its unique security protocol, "
|
||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
||||
"catering to a wide range of devices and operating systems. "
|
||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
||||
"which continually reinforces its security. "
|
||||
"With a strong balance of performance, security, and compatibility, "
|
||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Normal power consumption on mobile devices\n"
|
||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||
"* Recognised by DPI systems and therefore susceptible to blocking\n"
|
||||
"* Can operate over both TCP and UDP network protocols.") },
|
||||
QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
|
||||
"It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
|
||||
"and is continuously improved by the community due to its open-source nature. "
|
||||
"It provides a good balance between speed and security but is easily recognized by DPI systems, "
|
||||
"making it susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Normal battery consumption on mobile devices\n"
|
||||
"* Flexible customization for various devices and OS\n"
|
||||
"* Operates over both TCP and UDP protocols") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||
"* Configurable encryption protocol\n"
|
||||
QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
|
||||
"Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
|
||||
"Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available in AmneziaVPN only on desktop platforms\n"
|
||||
"* Customizable encryption protocol\n"
|
||||
"* Detectable by some DPI systems\n"
|
||||
"* Works over TCP network protocol.") },
|
||||
"* Operates over TCP protocol\n") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||
"protecting against detection.\n\n"
|
||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
||||
"and the server.\n\n"
|
||||
"Cloak protects OpenVPN from detection. \n\n"
|
||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||
"being detected\n\n"
|
||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||
"invisible to analysis systems.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
|
||||
"\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
|
||||
"\nThe Cloak plugin further protects the connection from DPI detection. "
|
||||
"It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
|
||||
"If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
|
||||
"\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* High power consumption on mobile devices\n"
|
||||
"* Flexible settings\n"
|
||||
"* Not recognised by detection systems\n"
|
||||
"* Works over TCP network protocol, 443 port.\n") },
|
||||
"* Flexible configuration options\n"
|
||||
"* Undetectable by DPI systems\n"
|
||||
"* Operates over TCP protocol on port 443") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
|
||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
|
||||
"It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
|
||||
"However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Low power consumption on mobile devices\n"
|
||||
"* Minimal configuration required\n"
|
||||
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
||||
"While WireGuard is known for its efficiency, "
|
||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
||||
"making its traffic blend in with regular internet traffic.\n"
|
||||
"This means that AmneziaWG keeps the fast performance of the original "
|
||||
"while adding an extra layer of stealth, "
|
||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Not recognised by traffic analysis systems\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
||||
"combining simplified architecture with high performance across all devices. "
|
||||
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
||||
"making VPN traffic indistinguishable from regular internet traffic.\n"
|
||||
"\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Low battery consumption on mobile devices\n"
|
||||
"* Minimal settings required\n"
|
||||
"* Undetectable by traffic analysis systems (DPI)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
|
||||
"It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, "
|
||||
"thus presenting an authentic TLS certificate and data. \n"
|
||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||
"legitimate sites without the need for specific configurations. \n"
|
||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
|
||||
"This makes REALITY a robust solution for maintaining internet freedom.")
|
||||
},
|
||||
QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
|
||||
"REALITY identifies censorship systems during the TLS handshake, "
|
||||
"redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
|
||||
"This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
|
||||
"\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
|
||||
"effectively protecting against DPI and other traffic analysis methods.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Resistant to active probing and DPI detection\n"
|
||||
"* No special configuration required to disguise traffic\n"
|
||||
"* Highly effective in heavily censored regions\n"
|
||||
"* Minimal battery consumption on devices\n"
|
||||
"* Operates over TCP protocol") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||
"making it particularly adaptive in dynamic network environments. \n"
|
||||
"While it offers a blend of security, stability, and speed, "
|
||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
||||
"* Available in the AmneziaVPN only on Windows\n"
|
||||
"* Low power consumption, on mobile devices\n"
|
||||
"* Minimal configuration\n"
|
||||
"* Recognised by DPI analysis systems\n"
|
||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
||||
QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. "
|
||||
"It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
|
||||
"While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available in AmneziaVPN only on Windows\n"
|
||||
"* Low battery consumption on mobile devices\n"
|
||||
"* Minimal configuration required\n"
|
||||
"* Detectable by DPI analysis systems(easily blocked)\n"
|
||||
"* Operates over UDP protocol(ports 500 and 4500)") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,9 @@ void CoreController::initModels()
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
|
||||
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
|
||||
|
||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||
|
||||
@@ -130,6 +133,9 @@ void CoreController::initControllers()
|
||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
|
||||
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
|
||||
|
||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||
|
||||
@@ -142,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()
|
||||
@@ -214,6 +223,9 @@ void CoreController::initSignalHandlers()
|
||||
initAutoConnectHandler();
|
||||
initAmneziaDnsToggledHandler();
|
||||
initPrepareConfigHandler();
|
||||
initImportPremiumV2VpnKeyHandler();
|
||||
initShowMigrationDrawerHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
}
|
||||
|
||||
void CoreController::initNotificationHandler()
|
||||
@@ -356,6 +368,31 @@ 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);
|
||||
}
|
||||
|
||||
QSharedPointer<PageController> CoreController::pageController() const
|
||||
{
|
||||
return m_pageController;
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
#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"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/focusController.h"
|
||||
@@ -18,6 +20,7 @@
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
|
||||
#include "ui/models/allowed_dns_model.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
@@ -80,6 +83,9 @@ private:
|
||||
void initAutoConnectHandler();
|
||||
void initAmneziaDnsToggledHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initImportPremiumV2VpnKeyHandler();
|
||||
void initShowMigrationDrawerHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
|
||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
@@ -102,9 +108,11 @@ private:
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||
QScopedPointer<AllowedDnsController> m_allowedDnsController;
|
||||
|
||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
@@ -112,6 +120,7 @@ private:
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
|
||||
|
||||
@@ -7,14 +7,20 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "core/api/apiUtils.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/ipcclient.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
@@ -30,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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,6 +63,17 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo
|
||||
|
||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QNetworkReply *reply;
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
@@ -101,6 +125,17 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
|
||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
@@ -278,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -439,15 +446,22 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode =
|
||||
ErrorCode error =
|
||||
runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut);
|
||||
if (errorCode)
|
||||
return errorCode;
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
if (stdOut.contains("doesn't work on cgroups v2"))
|
||||
return ErrorCode::ServerDockerOnCgroupsV2;
|
||||
if (stdOut.contains("cgroup mountpoint does not exist"))
|
||||
return ErrorCode::ServerCgroupMountpoint;
|
||||
|
||||
return errorCode;
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||
|
||||
@@ -58,6 +58,8 @@ namespace amnezia
|
||||
ServerUserDirectoryNotAccessible = 208,
|
||||
ServerUserNotAllowedInSudoers = 209,
|
||||
ServerUserPasswordRequired = 210,
|
||||
ServerDockerOnCgroupsV2 = 211,
|
||||
ServerCgroupMountpoint = 212,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
@@ -115,6 +117,8 @@ namespace amnezia
|
||||
ApiServicesMissingError = 1107,
|
||||
ApiConfigLimitError = 1108,
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -26,6 +26,8 @@ QString errorString(ErrorCode code) {
|
||||
case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break;
|
||||
case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break;
|
||||
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
|
||||
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
|
||||
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
@@ -72,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;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <winsock.h>
|
||||
#include <QNetworkInterface>
|
||||
#include "qendian.h"
|
||||
#include <QSettings>
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <arpa/inet.h>
|
||||
@@ -185,6 +186,17 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool NetworkUtilities::checkIpv6Enabled() {
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",
|
||||
QSettings::NativeFormat);
|
||||
int ret = RegHLM.value("DisabledComponents", 0).toInt();
|
||||
qDebug() << "Check for Windows disabled IPv6 return " << ret;
|
||||
return (ret != 255);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||
const ULONG Flags,
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||
static bool checkIPv4Format(const QString &ip);
|
||||
static bool checkIpSubnetFormat(const QString &ip);
|
||||
static bool checkIpv6Enabled();
|
||||
static QString getGatewayAndIface();
|
||||
// Returns the Interface Index that could Route to dst
|
||||
static int AdapterIndexTo(const QHostAddress& dst);
|
||||
@@ -29,7 +30,6 @@ public:
|
||||
|
||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||
|
||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -371,6 +370,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
|
||||
@@ -48,6 +48,13 @@ QJsonObject InterfaceConfig::toJson() const {
|
||||
}
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
|
||||
QJsonArray jsAllowedDnsServers;
|
||||
for (const QString& i : m_allowedDnsServers) {
|
||||
jsAllowedDnsServers.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("allowedDnsServers", jsAllowedDnsServers);
|
||||
|
||||
QJsonArray disabledApps;
|
||||
for (const QString& i : m_vpnDisabledApps) {
|
||||
disabledApps.append(QJsonValue(i));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define INTERFACECONFIG_H
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "ipaddress.h"
|
||||
@@ -37,6 +38,7 @@ class InterfaceConfig {
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
QStringList m_allowedDnsServers;
|
||||
bool m_killSwitchEnabled;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
|
||||
@@ -123,6 +123,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt();
|
||||
QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray();
|
||||
QJsonArray allowedDns = rawConfig.value(amnezia::config_key::allowedDnsServers).toArray();
|
||||
|
||||
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||
|
||||
@@ -226,6 +227,8 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
json.insert("vpnDisabledApps", splitTunnelApps);
|
||||
|
||||
json.insert("allowedDnsServers", allowedDns);
|
||||
|
||||
json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption));
|
||||
|
||||
if (protocolName == amnezia::config_key::awg) {
|
||||
|
||||
@@ -31,7 +31,9 @@ IPUtilsLinux::~IPUtilsLinux() {
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
|
||||
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
|
||||
bool ret = addIP4AddressToDevice(config);
|
||||
addIP6AddressToDevice(config);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||
@@ -95,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;
|
||||
}
|
||||
@@ -136,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;
|
||||
}
|
||||
|
||||
@@ -455,9 +455,6 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
|
||||
|
||||
void LinuxFirewall::updateAllowNets(const QStringList& servers)
|
||||
{
|
||||
static QStringList existingServers {};
|
||||
|
||||
existingServers = servers;
|
||||
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
|
||||
for (const QString& rule : getAllowRule(servers))
|
||||
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||
|
||||
@@ -182,7 +184,7 @@ bool WireguardUtilsLinux::deleteInterface() {
|
||||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
LinuxFirewall::uninstall();
|
||||
KillSwitch::instance()->disableKillSwitch();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||
|
||||
@@ -180,7 +182,7 @@ bool WireguardUtilsMacos::deleteInterface() {
|
||||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
MacOSFirewall::uninstall();
|
||||
KillSwitch::instance()->disableKillSwitch();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
#define IPV6_ADDRESS_SIZE 16
|
||||
|
||||
// ID for the Firewall Sublayer
|
||||
@@ -180,16 +182,29 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||
} \
|
||||
}
|
||||
|
||||
logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex;
|
||||
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
|
||||
if (vpnAdapterIndex < 0)
|
||||
{
|
||||
IPAddress allv4("0.0.0.0/0");
|
||||
if (!blockTrafficTo(allv4, MED_WEIGHT,
|
||||
"Block Internet", "killswitch")) {
|
||||
return false;
|
||||
}
|
||||
IPAddress allv6("::/0");
|
||||
if (!blockTrafficTo(allv6, MED_WEIGHT,
|
||||
"Block Internet", "killswitch")) {
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||
"Allow usage of VPN Adapter"));
|
||||
"Allow usage of VPN Adapter"));
|
||||
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
|
||||
FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic"));
|
||||
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic"));
|
||||
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
|
||||
"Allow all for AmneziaVPN.exe"));
|
||||
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
|
||||
FW_OK(
|
||||
allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1"));
|
||||
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
|
||||
"Allow Loopback traffic on device %1"));
|
||||
|
||||
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
|
||||
return true;
|
||||
@@ -226,6 +241,37 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow unprotected traffic sent to the following address ranges.
|
||||
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
disableKillSwitch();
|
||||
return false;
|
||||
}
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
disableKillSwitch();
|
||||
});
|
||||
|
||||
for (const QString& addr : ranges) {
|
||||
logger.debug() << "Allow killswitch exclude: " << addr;
|
||||
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
@@ -262,12 +308,20 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString& dns : config.m_allowedDnsServers) {
|
||||
logger.debug() << "Allow DNS: " << dns;
|
||||
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.m_excludedAddresses.empty()) {
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
logger.debug() << "excludedAddresses range: " << i;
|
||||
|
||||
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -313,37 +367,41 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
|
||||
}
|
||||
|
||||
bool WindowsFirewall::disableKillSwitch() {
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
return KillSwitch::instance()->disableKillSwitch();
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowAllTraffic() {
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
if (result != ERROR_SUCCESS) {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
}
|
||||
});
|
||||
if (result != ERROR_SUCCESS) {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& filterID : m_peerRules.values()) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
for (const auto& filterID : m_peerRules.values()) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
|
||||
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
|
||||
// Commit!
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
m_peerRules.clear();
|
||||
m_activeRules.clear();
|
||||
logger.debug() << "Firewall Disabled!";
|
||||
return true;
|
||||
// Commit!
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
m_peerRules.clear();
|
||||
m_activeRules.clear();
|
||||
logger.debug() << "Firewall Disabled!";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||
|
||||
@@ -43,6 +43,8 @@ class WindowsFirewall final : public QObject {
|
||||
bool enablePeerTraffic(const InterfaceConfig& config);
|
||||
bool disablePeerTraffic(const QString& pubkey);
|
||||
bool disableKillSwitch();
|
||||
bool allowAllTraffic();
|
||||
bool allowTrafficRange(const QStringList& ranges);
|
||||
|
||||
private:
|
||||
static bool initSublayer();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowscommons.h"
|
||||
#include "windowsdaemon.h"
|
||||
#include "windowsfirewall.h"
|
||||
|
||||
#pragma comment(lib, "iphlpapi.lib")
|
||||
@@ -269,6 +267,13 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
||||
if (result == ERROR_OBJECT_ALREADY_EXISTS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case for ipv6 route with disabled ipv6
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol
|
||||
&& result == ERROR_NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result != NO_ERROR) {
|
||||
logger.error() << "Failed to create route to"
|
||||
<< prefix.toString()
|
||||
|
||||
@@ -171,6 +171,11 @@ ErrorCode OpenVpnProtocol::start()
|
||||
return lastError();
|
||||
}
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress(
|
||||
m_configData.value(amnezia::config_key::hostName).toString())));
|
||||
#endif
|
||||
|
||||
// Detect default gateway
|
||||
#ifdef Q_OS_MAC
|
||||
QProcess p;
|
||||
|
||||
@@ -95,6 +95,8 @@ namespace amnezia
|
||||
constexpr char splitTunnelApps[] = "splitTunnelApps";
|
||||
constexpr char appSplitTunnelType[] = "appSplitTunnelType";
|
||||
|
||||
constexpr char allowedDnsServers[] = "allowedDnsServers";
|
||||
|
||||
constexpr char killSwitchOption[] = "killSwitchOption";
|
||||
|
||||
constexpr char crc[] = "crc";
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
|
||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||
<file>ui/qml/Components/AddSitePanel.qml</file>
|
||||
<file>ui/qml/Config/GlobalConfig.qml</file>
|
||||
<file>ui/qml/Config/qmldir</file>
|
||||
<file>ui/qml/Controls2/BackButtonType.qml</file>
|
||||
@@ -143,7 +144,9 @@
|
||||
<file>ui/qml/Controls2/DropDownType.qml</file>
|
||||
<file>ui/qml/Controls2/FlickableType.qml</file>
|
||||
<file>ui/qml/Controls2/Header2Type.qml</file>
|
||||
<file>ui/qml/Controls2/HeaderType.qml</file>
|
||||
<file>ui/qml/Controls2/BaseHeaderType.qml</file>
|
||||
<file>ui/qml/Controls2/HeaderTypeWithButton.qml</file>
|
||||
<file>ui/qml/Controls2/HeaderTypeWithSwitcher.qml</file>
|
||||
<file>ui/qml/Controls2/HorizontalRadioButton.qml</file>
|
||||
<file>ui/qml/Controls2/ImageButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/LabelWithButtonType.qml</file>
|
||||
@@ -199,6 +202,8 @@
|
||||
<file>ui/qml/Pages2/PageSettingsBackup.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsConnection.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsDns.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsKillSwitch.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerData.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerInfo.qml</file>
|
||||
@@ -231,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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "secure_qsettings.h"
|
||||
|
||||
#include "QAead.h"
|
||||
#include "QBlockCipher.h"
|
||||
#include "../client/3rd/QSimpleCrypto/src/include/QAead.h"
|
||||
#include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h"
|
||||
#include "utilities.h"
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
|
||||
#include "keychain.h"
|
||||
#include "../client/3rd/qtkeychain/qtkeychain/keychain.h"
|
||||
|
||||
class SecureQSettings : public QObject
|
||||
{
|
||||
|
||||
@@ -443,6 +443,16 @@ void Settings::setKillSwitchEnabled(bool enabled)
|
||||
setValue("Conf/killSwitchEnabled", enabled);
|
||||
}
|
||||
|
||||
bool Settings::isStrictKillSwitchEnabled() const
|
||||
{
|
||||
return value("Conf/strictKillSwitchEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void Settings::setStrictKillSwitchEnabled(bool enabled)
|
||||
{
|
||||
setValue("Conf/strictKillSwitchEnabled", enabled);
|
||||
}
|
||||
|
||||
QString Settings::getInstallationUuid(const bool needCreate)
|
||||
{
|
||||
auto uuid = value("Conf/installationUuid", "").toString();
|
||||
@@ -548,3 +558,23 @@ 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();
|
||||
}
|
||||
|
||||
void Settings::setAllowedDnsServers(const QStringList &servers)
|
||||
{
|
||||
setValue("Conf/allowedDnsServers", servers);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,10 @@ public:
|
||||
|
||||
bool isKillSwitchEnabled() const;
|
||||
void setKillSwitchEnabled(bool enabled);
|
||||
|
||||
bool isStrictKillSwitchEnabled() const;
|
||||
void setStrictKillSwitchEnabled(bool enabled);
|
||||
|
||||
QString getInstallationUuid(const bool needCreate);
|
||||
|
||||
void resetGatewayEndpoint();
|
||||
@@ -225,6 +229,12 @@ public:
|
||||
bool isHomeAdLabelVisible();
|
||||
void disableHomeAdLabel();
|
||||
|
||||
bool isPremV1MigrationReminderActive();
|
||||
void disablePremV1MigrationReminder();
|
||||
|
||||
QStringList allowedDnsServers() const;
|
||||
void setAllowedDnsServers(const QStringList &servers);
|
||||
|
||||
signals:
|
||||
void saveLogsChanged(bool enabled);
|
||||
void screenshotsEnabledChanged(bool enabled);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
101
client/ui/controllers/allowedDnsController.cpp
Normal file
101
client/ui/controllers/allowedDnsController.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "allowedDnsController.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "systemController.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
AllowedDnsController::AllowedDnsController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<AllowedDnsModel> &allowedDnsModel,
|
||||
QObject *parent)
|
||||
: QObject(parent), m_settings(settings), m_allowedDnsModel(allowedDnsModel)
|
||||
{
|
||||
}
|
||||
|
||||
void AllowedDnsController::addDns(QString ip)
|
||||
{
|
||||
if (ip.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) {
|
||||
emit errorOccurred(tr("The address does not look like a valid IP address"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_allowedDnsModel->addDns(ip)) {
|
||||
emit finished(tr("New DNS server added: %1").arg(ip));
|
||||
} else {
|
||||
emit errorOccurred(tr("DNS server already exists: %1").arg(ip));
|
||||
}
|
||||
}
|
||||
|
||||
void AllowedDnsController::removeDns(int index)
|
||||
{
|
||||
auto modelIndex = m_allowedDnsModel->index(index);
|
||||
auto ip = m_allowedDnsModel->data(modelIndex, AllowedDnsModel::Roles::IpRole).toString();
|
||||
m_allowedDnsModel->removeDns(modelIndex);
|
||||
|
||||
emit finished(tr("DNS server removed: %1").arg(ip));
|
||||
}
|
||||
|
||||
void AllowedDnsController::importDns(const QString &fileName, bool replaceExisting)
|
||||
{
|
||||
QByteArray jsonData;
|
||||
if (!SystemController::readFile(fileName, jsonData)) {
|
||||
emit errorOccurred(tr("Can't open file: %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData);
|
||||
if (jsonDocument.isNull()) {
|
||||
emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDocument.isArray()) {
|
||||
emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
auto jsonArray = jsonDocument.array();
|
||||
QStringList dnsServers;
|
||||
|
||||
for (auto jsonValue : jsonArray) {
|
||||
auto ip = jsonValue.toString();
|
||||
|
||||
if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) {
|
||||
qDebug() << ip << " is not a valid IP address";
|
||||
continue;
|
||||
}
|
||||
|
||||
dnsServers.append(ip);
|
||||
}
|
||||
|
||||
m_allowedDnsModel->addDnsList(dnsServers, replaceExisting);
|
||||
|
||||
emit finished(tr("Import completed"));
|
||||
}
|
||||
|
||||
void AllowedDnsController::exportDns(const QString &fileName)
|
||||
{
|
||||
auto dnsServers = m_allowedDnsModel->getCurrentDnsServers();
|
||||
|
||||
QJsonArray jsonArray;
|
||||
|
||||
for (const auto &ip : dnsServers) {
|
||||
jsonArray.append(ip);
|
||||
}
|
||||
|
||||
QJsonDocument jsonDocument(jsonArray);
|
||||
QByteArray jsonData = jsonDocument.toJson();
|
||||
|
||||
SystemController::saveFile(fileName, jsonData);
|
||||
|
||||
emit finished(tr("Export completed"));
|
||||
}
|
||||
35
client/ui/controllers/allowedDnsController.h
Normal file
35
client/ui/controllers/allowedDnsController.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef ALLOWEDDNSCONTROLLER_H
|
||||
#define ALLOWEDDNSCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "settings.h"
|
||||
#include "ui/models/allowed_dns_model.h"
|
||||
|
||||
class AllowedDnsController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AllowedDnsController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<AllowedDnsModel> &allowedDnsModel,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void addDns(QString ip);
|
||||
void removeDns(int index);
|
||||
|
||||
void importDns(const QString &fileName, bool replaceExisting);
|
||||
void exportDns(const QString &fileName);
|
||||
|
||||
signals:
|
||||
void errorOccurred(const QString &errorMessage);
|
||||
void finished(const QString &message);
|
||||
|
||||
void saveFile(const QString &fileName, const QString &data);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||
};
|
||||
|
||||
#endif // ALLOWEDDNSCONTROLLER_H
|
||||
@@ -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;
|
||||
@@ -274,7 +283,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);
|
||||
@@ -304,7 +314,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);
|
||||
@@ -323,6 +334,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);
|
||||
@@ -339,7 +351,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);
|
||||
@@ -358,6 +371,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);
|
||||
|
||||
133
client/ui/controllers/api/apiPremV1MigrationController.cpp
Normal file
133
client/ui/controllers/api/apiPremV1MigrationController.cpp
Normal 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();
|
||||
}
|
||||
50
client/ui/controllers/api/apiPremV1MigrationController.h
Normal file
50
client/ui/controllers/api/apiPremV1MigrationController.h
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -665,27 +665,27 @@ void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig)
|
||||
containerConfig[ProtocolProps::protoToString(Proto::OpenVpn)].toObject()[config_key::last_config].toString();
|
||||
QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[config_key::config].toString();
|
||||
|
||||
const QRegularExpression regExp { "(\\w+-\\w+|\\w+)" };
|
||||
const size_t dangerousTagsMaxCount = 3;
|
||||
|
||||
// https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst
|
||||
QStringList dangerousTags {
|
||||
"up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify"
|
||||
};
|
||||
|
||||
QStringList maliciousStrings;
|
||||
QStringList lines = protocolConfigJson.replace("\r", "").split("\n");
|
||||
for (const QString &l : lines) {
|
||||
QRegularExpressionMatch match = regExp.match(l);
|
||||
if (dangerousTags.contains(match.captured(0))) {
|
||||
maliciousStrings << l;
|
||||
QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts);
|
||||
|
||||
for (const QString &rawLine : lines) {
|
||||
QString line = rawLine.trimmed();
|
||||
|
||||
QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty);
|
||||
if (dangerousTags.contains(command, Qt::CaseInsensitive)) {
|
||||
maliciousStrings << rawLine;
|
||||
}
|
||||
}
|
||||
|
||||
m_maliciousWarningText = tr("This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious "
|
||||
"scripts, so only add it if you fully trust the provider of this config. ");
|
||||
|
||||
if (maliciousStrings.size() >= dangerousTagsMaxCount) {
|
||||
if (!maliciousStrings.isEmpty()) {
|
||||
m_maliciousWarningText.push_back(tr("<br>In the imported configuration, potentially dangerous lines were found:"));
|
||||
for (const auto &string : maliciousStrings) {
|
||||
m_maliciousWarningText.push_back(QString("<br><i>%1</i>").arg(string));
|
||||
|
||||
@@ -363,7 +363,8 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
|
||||
QJsonObject config;
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||
const auto &protocols = ContainerProps::protocolsForContainer(container);
|
||||
for (const auto &protocol : protocols) {
|
||||
QJsonObject containerConfig;
|
||||
if (protocol == mainProto) {
|
||||
containerConfig.insert(config_key::port, port);
|
||||
@@ -387,6 +388,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
}
|
||||
}
|
||||
|
||||
containerConfig[config_key::subnet_address] = serverConfigMap.value("Address").remove("/24");
|
||||
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
|
||||
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
|
||||
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
|
||||
@@ -398,6 +400,25 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
serverConfigMap.value(config_key::underloadPacketMagicHeader);
|
||||
containerConfig[config_key::transportPacketMagicHeader] =
|
||||
serverConfigMap.value(config_key::transportPacketMagicHeader);
|
||||
|
||||
} else if (protocol == Proto::WireGuard) {
|
||||
QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
|
||||
protocols::wireguard::serverConfigPath, errorCode);
|
||||
|
||||
QMap<QString, QString> serverConfigMap;
|
||||
auto serverConfigLines = serverConfig.split("\n");
|
||||
for (auto &line : serverConfigLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
containerConfig[config_key::subnet_address] = serverConfigMap.value("Address").remove("/24");
|
||||
} else if (protocol == Proto::Sftp) {
|
||||
stdOut.clear();
|
||||
script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name);
|
||||
@@ -432,6 +453,51 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
containerConfig.insert(config_key::userName, userName);
|
||||
containerConfig.insert(config_key::password, password);
|
||||
}
|
||||
} else if (protocol == Proto::Xray) {
|
||||
QString currentConfig = serverController->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
qDebug() << doc;
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return errorCode;
|
||||
}
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
if (!serverConfig.contains("inbounds")) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig["inbounds"].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains("streamSettings")) {
|
||||
logger.error() << "Inbound missing 'streamSettings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject streamSettings = inbound["streamSettings"].toObject();
|
||||
QJsonObject realitySettings = streamSettings["realitySettings"].toObject();
|
||||
if (!realitySettings.contains("serverNames")) {
|
||||
logger.error() << "Settings missing 'clients' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QString siteName = realitySettings["serverNames"][0].toString();
|
||||
qDebug() << siteName;
|
||||
|
||||
containerConfig.insert(config_key::site, siteName);
|
||||
}
|
||||
|
||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
|
||||
@@ -31,13 +31,15 @@ namespace PageLoader
|
||||
PageSettingsLogging,
|
||||
PageSettingsSplitTunneling,
|
||||
PageSettingsAppSplitTunneling,
|
||||
PageSettingsKillSwitch,
|
||||
PageSettingsApiServerInfo,
|
||||
PageSettingsApiAvailableCountries,
|
||||
PageSettingsApiSupport,
|
||||
PageSettingsApiInstructions,
|
||||
PageSettingsApiNativeConfigs,
|
||||
PageSettingsApiDevices,
|
||||
|
||||
PageSettingsKillSwitchExceptions,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
PageServiceDnsSettings,
|
||||
|
||||
@@ -245,6 +245,23 @@ bool SettingsController::isKillSwitchEnabled()
|
||||
void SettingsController::toggleKillSwitch(bool enable)
|
||||
{
|
||||
m_settings->setKillSwitchEnabled(enable);
|
||||
emit killSwitchEnabledChanged();
|
||||
if (enable == false) {
|
||||
emit strictKillSwitchEnabledChanged(false);
|
||||
} else {
|
||||
emit strictKillSwitchEnabledChanged(isStrictKillSwitchEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsController::isStrictKillSwitchEnabled()
|
||||
{
|
||||
return m_settings->isStrictKillSwitchEnabled();
|
||||
}
|
||||
|
||||
void SettingsController::toggleStrictKillSwitch(bool enable)
|
||||
{
|
||||
m_settings->setStrictKillSwitchEnabled(enable);
|
||||
emit strictKillSwitchEnabledChanged(enable);
|
||||
}
|
||||
|
||||
bool SettingsController::isNotificationPermissionGranted()
|
||||
|
||||
@@ -24,6 +24,8 @@ public:
|
||||
Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged)
|
||||
Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged)
|
||||
Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged)
|
||||
Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged)
|
||||
Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged)
|
||||
|
||||
Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled)
|
||||
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
|
||||
@@ -75,6 +77,9 @@ public slots:
|
||||
bool isKillSwitchEnabled();
|
||||
void toggleKillSwitch(bool enable);
|
||||
|
||||
bool isStrictKillSwitchEnabled();
|
||||
void toggleStrictKillSwitch(bool enable);
|
||||
|
||||
bool isNotificationPermissionGranted();
|
||||
void requestNotificationPermission();
|
||||
|
||||
@@ -98,6 +103,8 @@ signals:
|
||||
void primaryDnsChanged();
|
||||
void secondaryDnsChanged();
|
||||
void loggingStateChanged();
|
||||
void killSwitchEnabledChanged();
|
||||
void strictKillSwitchEnabledChanged(bool enabled);
|
||||
|
||||
void restoreBackupFinished();
|
||||
void changeSettingsFinished(const QString &finishedMessage);
|
||||
|
||||
86
client/ui/models/allowed_dns_model.cpp
Normal file
86
client/ui/models/allowed_dns_model.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "allowed_dns_model.h"
|
||||
|
||||
AllowedDnsModel::AllowedDnsModel(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: QAbstractListModel(parent), m_settings(settings)
|
||||
{
|
||||
fillDnsServers();
|
||||
}
|
||||
|
||||
int AllowedDnsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_dnsServers.size();
|
||||
}
|
||||
|
||||
QVariant AllowedDnsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case IpRole:
|
||||
return m_dnsServers.at(index.row());
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool AllowedDnsModel::addDns(const QString &ip)
|
||||
{
|
||||
if (m_dnsServers.contains(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_dnsServers.append(ip);
|
||||
m_settings->setAllowedDnsServers(m_dnsServers);
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AllowedDnsModel::addDnsList(const QStringList &dnsServers, bool replaceExisting)
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
if (replaceExisting) {
|
||||
m_dnsServers.clear();
|
||||
}
|
||||
|
||||
for (const QString &ip : dnsServers) {
|
||||
if (!m_dnsServers.contains(ip)) {
|
||||
m_dnsServers.append(ip);
|
||||
}
|
||||
}
|
||||
|
||||
m_settings->setAllowedDnsServers(m_dnsServers);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void AllowedDnsModel::removeDns(QModelIndex index)
|
||||
{
|
||||
if (!index.isValid() || index.row() >= m_dnsServers.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), index.row(), index.row());
|
||||
m_dnsServers.removeAt(index.row());
|
||||
m_settings->setAllowedDnsServers(m_dnsServers);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
QStringList AllowedDnsModel::getCurrentDnsServers()
|
||||
{
|
||||
return m_dnsServers;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> AllowedDnsModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[IpRole] = "ip";
|
||||
return roles;
|
||||
}
|
||||
|
||||
void AllowedDnsModel::fillDnsServers()
|
||||
{
|
||||
m_dnsServers = m_settings->allowedDnsServers();
|
||||
}
|
||||
37
client/ui/models/allowed_dns_model.h
Normal file
37
client/ui/models/allowed_dns_model.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef ALLOWEDDNSMODEL_H
|
||||
#define ALLOWEDDNSMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include "settings.h"
|
||||
|
||||
class AllowedDnsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
IpRole = Qt::UserRole + 1
|
||||
};
|
||||
|
||||
explicit AllowedDnsModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
public slots:
|
||||
bool addDns(const QString &ip);
|
||||
void addDnsList(const QStringList &dnsServers, bool replaceExisting);
|
||||
void removeDns(QModelIndex index);
|
||||
QStringList getCurrentDnsServers();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
void fillDnsServers();
|
||||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QStringList m_dnsServers;
|
||||
};
|
||||
|
||||
#endif // ALLOWEDDNSMODEL_H
|
||||
@@ -69,7 +69,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
"Access all websites and online resources. Speeds up to %1 Mbps.")
|
||||
.arg(speed);
|
||||
} else if (serviceType == serviceType::amneziaFree) {
|
||||
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||
QString description = tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
|
||||
if (!isServiceAvailable) {
|
||||
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
|
||||
"return to the previous screen, and try again.</a>");
|
||||
@@ -82,7 +82,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
|
||||
"Access all websites and online resources.");
|
||||
} else {
|
||||
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||
return tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
|
||||
}
|
||||
}
|
||||
case IsServiceAvailableRole: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ class XrayConfigModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
SiteRole
|
||||
SiteRole,
|
||||
PortRole
|
||||
};
|
||||
|
||||
explicit XrayConfigModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -348,6 +348,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
73
client/ui/qml/Components/AddSitePanel.qml
Normal file
73
client/ui/qml/Components/AddSitePanel.qml
Normal file
@@ -0,0 +1,73 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool enabled: true
|
||||
property string placeholderText: ""
|
||||
property alias textField: searchField.textField
|
||||
|
||||
signal addClicked(string text)
|
||||
signal moreClicked()
|
||||
|
||||
implicitWidth: 360
|
||||
implicitHeight: 96
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: "#0E0F12"
|
||||
opacity: 0.85
|
||||
z: -1
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: addSiteButton
|
||||
|
||||
enabled: root.enabled
|
||||
spacing: 2
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 16
|
||||
leftMargin: 16
|
||||
rightMargin: 16
|
||||
bottomMargin: 24
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
rightButtonClickedOnEnter: true
|
||||
|
||||
textField.placeholderText: root.placeholderText
|
||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||
|
||||
clickedFunc: function() {
|
||||
root.addClicked(textField.text)
|
||||
textField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
id: addSiteButtonImage
|
||||
implicitWidth: 56
|
||||
implicitHeight: 56
|
||||
|
||||
image: "qrc:/images/controls/more-vertical.svg"
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
onClicked: root.moreClicked()
|
||||
|
||||
Keys.onReturnPressed: addSiteButtonImage.clicked()
|
||||
Keys.onEnterPressed: addSiteButtonImage.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
194
client/ui/qml/Components/ApiPremV1MigrationDrawer.qml
Normal file
194
client/ui/qml/Components/ApiPremV1MigrationDrawer.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
client/ui/qml/Components/ApiPremV1SubListDrawer.qml
Normal file
89
client/ui/qml/Components/ApiPremV1SubListDrawer.qml
Normal 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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ ListView {
|
||||
property var selectedText
|
||||
|
||||
width: rootWidth
|
||||
height: contentItem.height
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
clip: true
|
||||
snapMode: ListView.SnapToItem
|
||||
|
||||
77
client/ui/qml/Components/OtpCodeDrawer.qml
Normal file
77
client/ui/qml/Components/OtpCodeDrawer.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
client/ui/qml/Controls2/BaseHeaderType.qml
Normal file
45
client/ui/qml/Controls2/BaseHeaderType.qml
Normal file
@@ -0,0 +1,45 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "TextTypes"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string headerText
|
||||
property int headerTextMaximumLineCount: 2
|
||||
property int headerTextElide: Qt.ElideRight
|
||||
property string descriptionText
|
||||
property alias headerRow: headerRow
|
||||
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
|
||||
Header1TextType {
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
text: root.headerText
|
||||
maximumLineCount: root.headerTextMaximumLineCount
|
||||
elide: root.headerTextElide
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
id: description
|
||||
Layout.topMargin: 16
|
||||
Layout.fillWidth: true
|
||||
text: root.descriptionText
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
visible: root.descriptionText !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,6 +239,7 @@ Item {
|
||||
sourceComponent: root.listView
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "TextTypes"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string actionButtonImage
|
||||
property var actionButtonFunction
|
||||
|
||||
property alias actionButton: headerActionButton
|
||||
|
||||
property string headerText
|
||||
property int headerTextMaximumLineCount: 2
|
||||
property int headerTextElide: Qt.ElideRight
|
||||
|
||||
property string descriptionText
|
||||
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
Header1TextType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: root.headerText
|
||||
maximumLineCount: root.headerTextMaximumLineCount
|
||||
elide: root.headerTextElide
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
id: headerActionButton
|
||||
|
||||
implicitWidth: 40
|
||||
implicitHeight: 40
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
image: root.actionButtonImage
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
visible: image ? true : false
|
||||
|
||||
onClicked: {
|
||||
if (actionButtonFunction && typeof actionButtonFunction === "function") {
|
||||
actionButtonFunction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
id: description
|
||||
|
||||
Layout.topMargin: 16
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: root.descriptionText
|
||||
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
|
||||
visible: root.descriptionText !== ""
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
if (actionButtonFunction && typeof actionButtonFunction === "function") {
|
||||
actionButtonFunction()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (actionButtonFunction && typeof actionButtonFunction === "function") {
|
||||
actionButtonFunction()
|
||||
}
|
||||
}
|
||||
}
|
||||
44
client/ui/qml/Controls2/HeaderTypeWithButton.qml
Normal file
44
client/ui/qml/Controls2/HeaderTypeWithButton.qml
Normal file
@@ -0,0 +1,44 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
BaseHeaderType {
|
||||
id: root
|
||||
|
||||
property string actionButtonImage
|
||||
property var actionButtonFunction
|
||||
property alias actionButton: headerActionButton
|
||||
|
||||
Component.onCompleted: {
|
||||
headerRow.children.push(headerActionButton)
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
id: headerActionButton
|
||||
implicitWidth: 40
|
||||
implicitHeight: 40
|
||||
Layout.alignment: Qt.AlignRight
|
||||
image: root.actionButtonImage
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
visible: image ? true : false
|
||||
|
||||
onClicked: {
|
||||
if (actionButtonFunction && typeof actionButtonFunction === "function") {
|
||||
actionButtonFunction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
if (actionButtonFunction && typeof actionButtonFunction === "function") {
|
||||
actionButtonFunction()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (actionButtonFunction && typeof actionButtonFunction === "function") {
|
||||
actionButtonFunction()
|
||||
}
|
||||
}
|
||||
}
|
||||
28
client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml
Normal file
28
client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml
Normal file
@@ -0,0 +1,28 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
BaseHeaderType {
|
||||
id: root
|
||||
|
||||
property var switcherFunction
|
||||
property bool showSwitcher: false
|
||||
property alias switcher: headerSwitcher
|
||||
|
||||
Component.onCompleted: {
|
||||
headerRow.children.push(headerSwitcher)
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: headerSwitcher
|
||||
Layout.alignment: Qt.AlignRight
|
||||
visible: root.showSwitcher
|
||||
|
||||
onToggled: {
|
||||
if (switcherFunction && typeof switcherFunction === "function") {
|
||||
switcherFunction(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,11 @@ RadioButton {
|
||||
property string selectedColor: AmneziaStyle.color.transparent
|
||||
|
||||
property string textColor: AmneziaStyle.color.paleGray
|
||||
property string textDisabledColor: AmneziaStyle.color.mutedGray
|
||||
property string selectedTextColor: AmneziaStyle.color.goldenApricot
|
||||
property string selectedTextDisabledColor: AmneziaStyle.color.burntOrange
|
||||
property string descriptionColor: AmneziaStyle.color.mutedGray
|
||||
property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray
|
||||
|
||||
property string borderFocusedColor: AmneziaStyle.color.paleGray
|
||||
property int borderFocusedWidth: 1
|
||||
@@ -30,6 +34,12 @@ RadioButton {
|
||||
|
||||
property bool isFocusable: true
|
||||
|
||||
|
||||
property string radioButtonInnerCirclePressedSource: "qrc:/images/controls/radio-button-inner-circle-pressed.png"
|
||||
property string radioButtonInnerCircleSource: "qrc:/images/controls/radio-button-inner-circle.png"
|
||||
property string radioButtonPressedSource: "qrc:/images/controls/radio-button-pressed.svg"
|
||||
property string radioButtonDefaultSource: "qrc:/images/controls/radio-button.svg"
|
||||
|
||||
Keys.onTabPressed: {
|
||||
FocusController.nextKeyTabItem()
|
||||
}
|
||||
@@ -94,14 +104,15 @@ RadioButton {
|
||||
if (showImage) {
|
||||
return imageSource
|
||||
} else if (root.pressed) {
|
||||
return "qrc:/images/controls/radio-button-inner-circle-pressed.png"
|
||||
return root.radioButtonInnerCirclePressedSource
|
||||
} else if (root.checked) {
|
||||
return "qrc:/images/controls/radio-button-inner-circle.png"
|
||||
return root.radioButtonInnerCircleSource
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
opacity: root.enabled ? 1.0 : 0.3
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 24
|
||||
@@ -113,12 +124,13 @@ RadioButton {
|
||||
if (showImage) {
|
||||
return ""
|
||||
} else if (root.pressed || root.checked) {
|
||||
return "qrc:/images/controls/radio-button-pressed.svg"
|
||||
return root.radioButtonPressedSource
|
||||
} else {
|
||||
return "qrc:/images/controls/radio-button.svg"
|
||||
return root.radioButtonDefaultSource
|
||||
}
|
||||
}
|
||||
|
||||
opacity: root.enabled ? 1.0 : 0.3
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 24
|
||||
@@ -148,10 +160,11 @@ RadioButton {
|
||||
elide: root.textElide
|
||||
|
||||
color: {
|
||||
if (root.checked) {
|
||||
return selectedTextColor
|
||||
if (root.enabled) {
|
||||
return root.checked ? selectedTextColor : textColor
|
||||
} else {
|
||||
return root.checked ? selectedTextDisabledColor : textDisabledColor
|
||||
}
|
||||
return textColor
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -164,7 +177,7 @@ RadioButton {
|
||||
CaptionTextType {
|
||||
id: description
|
||||
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
color: root.enabled ? root.descriptionColor : root.descriptionDisabledColor
|
||||
text: root.descriptionText
|
||||
|
||||
visible: root.descriptionText !== ""
|
||||
@@ -177,6 +190,7 @@ RadioButton {
|
||||
MouseArea {
|
||||
anchors.fill: root
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: false
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ PageType {
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ PageType {
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("AmneziaWG settings")
|
||||
|
||||
@@ -91,7 +91,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("AmneziaWG settings")
|
||||
|
||||
@@ -76,7 +76,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Cloak settings")
|
||||
|
||||
@@ -75,7 +75,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("OpenVPN settings")
|
||||
|
||||
@@ -32,7 +32,7 @@ PageType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
@@ -78,7 +78,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Shadowsocks settings")
|
||||
@@ -141,7 +141,7 @@ PageType {
|
||||
|
||||
for (var i = 0; i < cipherListView.model.count; i++) {
|
||||
if (cipherListView.model.get(i).name === cipherDropDown.text) {
|
||||
currentIndex = i
|
||||
cipherListView.selectedIndex = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("WG settings")
|
||||
|
||||
@@ -77,7 +77,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("WG settings")
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("XRay settings")
|
||||
}
|
||||
@@ -93,9 +93,9 @@ PageType {
|
||||
var tmpText = textField.text
|
||||
tmpText = tmpText.toLocaleLowerCase()
|
||||
|
||||
var indexHttps = tmpText.indexOf("https://")
|
||||
if (indexHttps === 0) {
|
||||
if (tmpText.startsWith("https://")) {
|
||||
tmpText = textField.text.substring(8)
|
||||
site = tmpText
|
||||
} else {
|
||||
site = textField.text
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -43,7 +43,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -85,7 +85,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
@@ -77,7 +77,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
@@ -217,7 +217,7 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("SOCKS5 settings")
|
||||
|
||||
@@ -54,7 +54,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
@@ -29,7 +29,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
@@ -69,7 +69,7 @@ PageType {
|
||||
Layout.topMargin: 20
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
HeaderTypeWithButton {
|
||||
id: headerContent
|
||||
objectName: "headerContent"
|
||||
|
||||
@@ -135,12 +135,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: containerRadioButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: false
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
if (checkable) {
|
||||
checked = true
|
||||
|
||||
@@ -35,7 +35,7 @@ PageType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -77,7 +77,7 @@ PageType {
|
||||
}
|
||||
|
||||
var headerText = qsTr("Are you sure you want to unlink this device?")
|
||||
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
|
||||
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing \"Reload API config\" in subscription settings on device.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ PageType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -38,7 +38,7 @@ PageType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -93,7 +93,7 @@ PageType {
|
||||
Layout.topMargin: 20
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
HeaderTypeWithButton {
|
||||
id: headerContent
|
||||
objectName: "headerContent"
|
||||
|
||||
@@ -333,7 +333,7 @@ PageType {
|
||||
|
||||
clickedFunc: function() {
|
||||
var headerText = qsTr("Are you sure you want to unlink this device?")
|
||||
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
|
||||
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing \"Reload API config\" in subscription settings on device.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ PageType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -88,6 +88,7 @@ PageType {
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
visible: link !== ""
|
||||
text: title
|
||||
descriptionText: description
|
||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||
|
||||
@@ -79,29 +79,22 @@ PageType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
HeaderTypeWithSwitcher {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("App split tunneling")
|
||||
headerText: qsTr("App split tunneling")
|
||||
|
||||
enabled: root.pageEnabled
|
||||
showSwitcher: true
|
||||
switcher {
|
||||
checked: AppSplitTunnelingModel.isTunnelingEnabled
|
||||
enabled: root.pageEnabled
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: switcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: root.pageEnabled
|
||||
|
||||
checked: AppSplitTunnelingModel.isTunnelingEnabled
|
||||
onToggled: {
|
||||
AppSplitTunnelingModel.toggleSplitTunneling(checked)
|
||||
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
|
||||
}
|
||||
switcherFunction: function(checked) {
|
||||
AppSplitTunnelingModel.toggleSplitTunneling(checked)
|
||||
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
@@ -60,7 +60,7 @@ PageType {
|
||||
|
||||
spacing: 16
|
||||
|
||||
HeaderType {
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Back up your configuration")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user