feature: added page for export api native configs

This commit is contained in:
vladimir.kuznetsov 2025-02-07 22:22:14 +07:00
parent 389c1f5327
commit 42d3d9b98a
30 changed files with 461 additions and 129 deletions

View file

@ -0,0 +1,106 @@
#include "ApiConfigsController.h"
#include "configurators/wireguard_configurator.h"
#include "core/api/apiDefs.h"
#include "core/controllers/gatewayController.h"
#include "version.h"
#include "ui/controllers/systemController.h"
namespace
{
namespace configKey
{
constexpr char cloak[] = "cloak";
constexpr char awg[] = "awg";
constexpr char apiEdnpoint[] = "api_endpoint";
constexpr char accessToken[] = "api_key";
constexpr char certificate[] = "certificate";
constexpr char publicKey[] = "public_key";
constexpr char uuid[] = "installation_uuid";
constexpr char osVersion[] = "os_version";
constexpr char appVersion[] = "app_version";
constexpr char userCountryCode[] = "user_country_code";
constexpr char serverCountryCode[] = "server_country_code";
constexpr char serviceType[] = "service_type";
constexpr char serviceInfo[] = "service_info";
constexpr char serviceProtocol[] = "service_protocol";
constexpr char aesKey[] = "aes_key";
constexpr char aesIv[] = "aes_iv";
constexpr char aesSalt[] = "aes_salt";
constexpr char apiPayload[] = "api_payload";
constexpr char keyPayload[] = "key_payload";
constexpr char apiConfig[] = "api_config";
constexpr char authData[] = "auth_data";
constexpr char config[] = "config";
}
}
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
}
void ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode);
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true);
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody);
// // if (errorCode != ErrorCode::NoError) {
// // }
QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object();
QString nativeConfig = jsonConfig.value(configKey::config).toString();
SystemController::saveFile(fileName, nativeConfig);
}
ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol)
{
ApiConfigsController::ApiPayloadData apiPayload;
if (protocol == configKey::cloak) {
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
} else if (protocol == configKey::awg) {
auto connData = WireguardConfigurator::genClientKeys();
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
}
return apiPayload;
}
QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData)
{
QJsonObject obj;
if (protocol == configKey::cloak) {
obj[configKey::certificate] = apiPayloadData.certRequest.request;
} else if (protocol == configKey::awg) {
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
}
obj[configKey::osVersion] = QSysInfo::productType();
obj[configKey::appVersion] = QString(APP_VERSION);
return obj;
}

View file

@ -0,0 +1,35 @@
#ifndef APICONFIGSCONTROLLER_H
#define APICONFIGSCONTROLLER_H
#include <QObject>
#include "configurators/openvpn_configurator.h"
#include "ui/models/servers_model.h"
class ApiConfigsController : public QObject
{
Q_OBJECT
public:
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
public slots:
void exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
private:
struct ApiPayloadData
{
OpenVpnConfigurator::ConnectionData certRequest;
QString wireGuardClientPrivKey;
QString wireGuardClientPubKey;
};
ApiPayloadData generateApiPayloadData(const QString &protocol);
QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData);
QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
};
#endif // APICONFIGSCONTROLLER_H

View file

@ -19,9 +19,14 @@ namespace
}
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel, const std::shared_ptr<Settings> &settings,
QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_settings(settings)
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
const QSharedPointer<ApiCountryModel> &apiCountryModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent),
m_serversModel(serversModel),
m_apiAccountInfoModel(apiAccountInfoModel),
m_apiCountryModel(apiCountryModel),
m_settings(settings)
{
}
@ -55,3 +60,8 @@ bool ApiSettingsController::getAccountInfo()
return true;
}
void ApiSettingsController::updateApiCountryModel()
{
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
}

View file

@ -4,6 +4,7 @@
#include <QObject>
#include "ui/models/api/apiAccountInfoModel.h"
#include "ui/models/apiCountryModel.h"
#include "ui/models/servers_model.h"
class ApiSettingsController : public QObject
@ -11,15 +12,18 @@ class ApiSettingsController : public QObject
Q_OBJECT
public:
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
~ApiSettingsController();
public slots:
bool getAccountInfo();
void updateApiCountryModel();
private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
std::shared_ptr<Settings> m_settings;
};

View file

@ -1,2 +0,0 @@
#include "importController.h"

View file

@ -1,24 +0,0 @@
#ifndef IMPORTCONTROLLER_H
#define IMPORTCONTROLLER_H
#include <QObject>
#include "ui/models/api/apiAccountInfoModel.h"
#include "ui/models/servers_model.h"
// namespace api
// {
// class ImportController : public QObject
// {
// Q_OBJECT
// public:
// ImportController(const QSharedPointer<ServersModel> &serversModel, QSharedPointer<AccountInfoModel> &accountInfoModel);
// ~ImportController();
// private:
// QSharedPointer<ServersModel> m_serversModel;
// QSharedPointer<AccountInfoModel> m_accountInfoModel;
// };
// }
#endif // IMPORTCONTROLLER_H

View file

@ -8,7 +8,7 @@
#include <QtConcurrent>
#include "core/controllers/vpnConfigurationController.h"
#include "core/enums/apiEnums.h"
#include "core/api/apiDefs.h"
#include "version.h"
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
@ -48,15 +48,15 @@ void ConnectionController::openConnection()
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
if (configVersion == ApiConfigSources::Telegram
if (configVersion == apiDefs::ConfigSource::Telegram
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromTelegram();
} else if (configVersion == ApiConfigSources::AmneziaGateway
} else if (configVersion == apiDefs::ConfigSource::AmneziaGateway
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event";
if (configVersion == ApiConfigSources::Telegram) {
if (configVersion == apiDefs::ConfigSource::Telegram) {
emit updateApiConfigFromTelegram();
} else {
emit updateApiConfigFromGateway();

View file

@ -35,6 +35,7 @@ namespace PageLoader
PageSettingsApiAvailableCountries,
PageSettingsApiSupport,
PageSettingsApiInstructions,
PageSettingsApiNativeConfigs,
PageServiceSftpSettings,
PageServiceTorWebsiteSettings,

View file

@ -12,9 +12,9 @@ namespace
namespace configKey
{
constexpr char availableCountries[] = "available_countries";
constexpr char serverCountryCode[] = "server_country_code";
constexpr char serverCountryName[] = "server_country_name";
constexpr char lastUpdated[] = "last_updated";
// constexpr char serverCountryCode[] = "server_country_code";
// constexpr char serverCountryName[] = "server_country_name";
// constexpr char lastUpdated[] = "last_updated";
constexpr char activeDeviceCount[] = "active_device_count";
constexpr char maxDeviceCount[] = "max_device_count";
constexpr char subscriptionEndDate[] = "subscription_end_date";
@ -38,7 +38,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
switch (role) {
case SubscriptionStatusRole: {
return ApiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
}
case EndDateRole: {
return QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
@ -46,9 +46,15 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
case ConnectedDevicesRole: {
return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount);
}
// case ServiceDescriptionRole: {
// return apiServiceData.serviceInfo.name;
// }
case ServiceDescriptionRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 "
"Mb/s");
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
"more. YouTube is not included in the free plan.");
}
}
}
return QVariant();
@ -60,21 +66,23 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
AccountInfoData accountInfoData;
auto availableCountries = accountInfoObject.value(configKey::availableCountries).toArray();
for (const auto &country : availableCountries) {
auto countryObject = country.toObject();
CountryInfo countryInfo;
countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString();
countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString();
countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString();
m_availableCountries = accountInfoObject.value(configKey::availableCountries).toArray();
// for (const auto &country : availableCountries) {
// auto countryObject = country.toObject();
// CountryInfo countryInfo;
// countryInfo.serverCountryCode = countryObject.value(configKey::serverCountryCode).toString();
// countryInfo.serverCountryName = countryObject.value(configKey::serverCountryName).toString();
// countryInfo.lastUpdated = countryObject.value(configKey::lastUpdated).toString();
accountInfoData.AvailableCountries.push_back(countryInfo);
}
// accountInfoData.AvailableCountries.push_back(countryInfo);
// }
accountInfoData.activeDeviceCount = accountInfoObject.value(configKey::activeDeviceCount).toInt();
accountInfoData.maxDeviceCount = accountInfoObject.value(configKey::maxDeviceCount).toInt();
accountInfoData.subscriptionEndDate = accountInfoObject.value(configKey::subscriptionEndDate).toString();
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
m_accountInfoData = accountInfoData;
endResetModel();
@ -93,6 +101,11 @@ QVariant ApiAccountInfoModel::data(const QString &roleString)
return {};
}
QJsonArray ApiAccountInfoModel::getAvailableCountries()
{
return m_availableCountries;
}
QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
{
QHash<int, QByteArray> roles;

View file

@ -5,6 +5,8 @@
#include <QJsonArray>
#include <QJsonObject>
#include "core/api/apiDefs.h"
class ApiAccountInfoModel : public QAbstractListModel
{
Q_OBJECT
@ -27,29 +29,23 @@ public slots:
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
QVariant data(const QString &roleString);
QJsonArray getAvailableCountries();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
struct CountryInfo
{
QString serverCountryCode;
QString serverCountryName;
QString lastUpdated;
};
struct AccountInfoData
{
QString subscriptionEndDate;
int activeDeviceCount;
int maxDeviceCount;
QString vpnKey;
QVector<CountryInfo> AvailableCountries;
apiDefs::ConfigType configType;
};
AccountInfoData m_accountInfoData;
QJsonArray m_availableCountries;
};
#endif // APIACCOUNTINFOMODEL_H

View file

@ -1,7 +1,7 @@
#include "servers_model.h"
#include "core/api/apiDefs.h"
#include "core/controllers/serverController.h"
#include "core/enums/apiEnums.h"
#include "core/networkUtilities.h"
#ifdef Q_OS_IOS
@ -132,10 +132,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
return serverHasInstalledContainers(index.row());
}
case IsServerFromTelegramApiRole: {
return server.value(config_key::configVersion).toInt() == ApiConfigSources::Telegram;
return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::Telegram;
}
case IsServerFromGatewayApiRole: {
return server.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway;
return server.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway;
}
case ApiConfigRole: {
return apiConfig;

View file

@ -112,11 +112,13 @@ ListView {
ServersModel.processedIndex = index
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
ApiSettingsController.getAccountInfo()
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
}
} else {

View file

@ -299,11 +299,13 @@ PageType {
ServersModel.processedIndex = ServersModel.defaultIndex
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
ApiSettingsController.getAccountInfo()
if (ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
PageController.goToPage(PageEnum.PageSettingsApiAvailableCountries)
} else {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
}
} else {

View file

@ -92,6 +92,10 @@ PageType {
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
actionButtonFunction: function() {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
}
}

View file

@ -20,31 +20,67 @@ PageType {
id: windows
readonly property string title: qsTr("Windows")
readonly property string imageSource: "qrc:/images/controls/external-link.svg"
readonly property var handler: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
}
readonly property string link: qsTr("")
}
QtObject {
id: macos
readonly property string title: qsTr("macOS")
readonly property string link: qsTr("")
}
QtObject {
id: android
readonly property string title: qsTr("Android")
readonly property string link: qsTr("")
}
QtObject {
id: androidTv
readonly property string title: qsTr("AndroidTV")
readonly property string link: qsTr("")
}
QtObject {
id: ios
readonly property string title: qsTr("iOS")
readonly property string link: qsTr("")
}
QtObject {
id: linux
readonly property string title: qsTr("Windows")
readonly property string imageSource: "qrc:/images/controls/external-link.svg"
readonly property var handler: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
}
readonly property string title: qsTr("Linux")
readonly property string link: qsTr("")
}
QtObject {
id: routers
readonly property string title: qsTr("Routers")
readonly property string link: qsTr("")
}
property list<QtObject> instructionsModel: [
windows,
linux
macos,
android,
androidTv,
ios,
linux,
routers
]
ListViewType {
id: listView
anchors.fill: parent
anchors.topMargin: 20
anchors.bottomMargin: 24
model: instructionsModel
@ -62,8 +98,8 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: "Support"
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
headerText: qsTr("How to connect on another device")
descriptionText: qsTr("Instructions on the Amnezia website")
}
}
@ -76,9 +112,11 @@ PageType {
Layout.topMargin: 6
text: title
leftImageSource: imageSource
rightImageSource: "qrc:/images/controls/external-link.svg"
clickedFunction: handler
clickedFunction: function() {
Qt.openUrlExternally(link)
}
}
DividerType {}

View file

@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import SortFilterProxyModel 0.2
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
property string configExtension: ".conf"
property string configCaption: qsTr("Save AmneziaVPN config")
ListViewType {
id: listView
anchors.fill: parent
anchors.topMargin: 20
anchors.bottomMargin: 24
model: ApiCountryModel
header: ColumnLayout {
width: listView.width
BackButtonType {
id: backButton
}
HeaderType {
id: header
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Configuration files")
descriptionText: qsTr("To connect a router or AmneziaWG application")
}
}
delegate: ColumnLayout {
width: listView.width
LabelWithButtonType {
id: telegramButton
Layout.fillWidth: true
Layout.topMargin: 6
text: countryName
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
rightImageSource: "qrc:/images/controls/download.svg"
clickedFunction: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = countryCode + configExtension
} else {
fileName = SystemController.getFileName(configCaption,
qsTr("Config files (*" + configExtension + ")"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode,
true,
configExtension)
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
ApiConfigsController.exportNativeConfig(countryCode, fileName)
PageController.showBusyIndicator(false)
}
}
}
DividerType {}
}
}
}

View file

@ -105,7 +105,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
descriptionText: ApiAccountInfoModel.data("serviceDescription")
actionButtonFunction: function() {
serverNameEditDrawer.openTriggered()
@ -145,6 +145,8 @@ PageType {
spacing: 0
LabelWithButtonType {
id: vpnKey
Layout.fillWidth: true
Layout.topMargin: 32
@ -157,17 +159,22 @@ PageType {
}
}
DividerType {}
DividerType {
visible: false
}
LabelWithButtonType {
Layout.fillWidth: true
Layout.topMargin: vpnKey.visible ? 0 : 32
text: qsTr("Configuration files")
descriptionText: qsTr("To connect the router")
descriptionText: qsTr("To connect a router or AmneziaWG application")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ApiSettingsController.updateApiCountryModel()
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
}
}

View file

@ -37,7 +37,7 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: "Support"
headerText: qsTr("Support")
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
}

View file

@ -95,7 +95,9 @@ PageType {
ServersModel.processedIndex = index
if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
PageController.showBusyIndicator(true)
ApiSettingsController.getAccountInfo()
PageController.showBusyIndicator(false)
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
} else {