Skip to content

Commit

Permalink
feature: added share vpn key to subscription settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
Nethius committed Feb 12, 2025
1 parent 07baf0e commit db31642
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 52 deletions.
1 change: 1 addition & 0 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION
core/api/apiDefs.h
core/qrCodeUtils.h core/qrCodeUtils.cpp
)

if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
Expand Down
2 changes: 2 additions & 0 deletions client/core/api/apiDefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ namespace apiDefs

constexpr QLatin1String apiConfig("api_config");
constexpr QLatin1String stackType("stack_type");

constexpr QLatin1String vpnKey("vpn_key");
}

const int requestTimeoutMsecs = 12 * 1000; // 12 secs
Expand Down
3 changes: 0 additions & 3 deletions client/core/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

namespace amnezia
{

constexpr const qint16 qrMagicCode = 1984;

struct ServerCredentials
{
QString hostName;
Expand Down
35 changes: 35 additions & 0 deletions client/core/qrCodeUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "qrCodeUtils.h"

#include <QIODevice>
#include <QList>

QList<QString> qrCodeUtuls::generateQrCodeImageSeries(const QByteArray &data)
{
double k = 850;

quint8 chunksCount = std::ceil(data.size() / k);
QList<QString> chunks;
for (int i = 0; i < data.size(); i = i + k) {
QByteArray chunk;
QDataStream s(&chunk, QIODevice::WriteOnly);
s << qrCodeUtuls::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k);

QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);

qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
QString svg = QString::fromStdString(toSvgString(qr, 1));
chunks.append(svgToBase64(svg));
}

return chunks;
}

QString qrCodeUtuls::svgToBase64(const QString &image)
{
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());
}

qrcodegen::QrCode qrCodeUtuls::generateQrCode(const QByteArray &data)
{
return qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW);
}
17 changes: 17 additions & 0 deletions client/core/qrCodeUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef QRCODEUTILS_H
#define QRCODEUTILS_H

#include <QString>

#include "qrcodegen.hpp"

namespace qrCodeUtuls
{
constexpr const qint16 qrMagicCode = 1984;

QList<QString> generateQrCodeImageSeries(const QByteArray &data);
qrcodegen::QrCode generateQrCode(const QByteArray &data);
QString svgToBase64(const QString &image);
};

#endif // QRCODEUTILS_H
30 changes: 27 additions & 3 deletions client/ui/controllers/api/apiConfigsController.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include "ApiConfigsController.h"
#include "apiConfigsController.h"

#include "configurators/wireguard_configurator.h"
#include "core/api/apiDefs.h"
#include "core/controllers/gatewayController.h"
#include "version.h"
#include "core/qrCodeUtils.h"
#include "ui/controllers/systemController.h"
#include "version.h"

namespace
{
Expand Down Expand Up @@ -43,7 +44,7 @@ namespace
}

ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent)
QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
}
Expand Down Expand Up @@ -84,6 +85,19 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
return true;
}

void ApiConfigsController::prepareVpnKeyExport()
{
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();

auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();

auto qr = qrCodeUtuls::generateQrCode(vpnKey.toUtf8());
m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));

emit vpnKeyExportReady();
}

ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol)
{
ApiConfigsController::ApiPayloadData apiPayload;
Expand Down Expand Up @@ -111,3 +125,13 @@ QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const

return obj;
}

QList<QString> ApiConfigsController::getQrCodes()
{
return m_qrCodes;
}

int ApiConfigsController::getQrCodesCount()
{
return m_qrCodes.size();
}
11 changes: 11 additions & 0 deletions client/ui/controllers/api/apiConfigsController.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ class ApiConfigsController : public QObject
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);

Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY vpnKeyExportReady)
Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady)

public slots:
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
// bool exportVpnKey(const QString &fileName);
void prepareVpnKeyExport();

signals:
void errorOccurred(ErrorCode errorCode);
void vpnKeyExportReady();

private:
struct ApiPayloadData
Expand All @@ -31,6 +37,11 @@ public slots:
ApiPayloadData generateApiPayloadData(const QString &protocol);
QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData);

QList<QString> getQrCodes();
int getQrCodesCount();

QList<QString> m_qrCodes;

QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
};
Expand Down
46 changes: 10 additions & 36 deletions client/ui/controllers/exportController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
#include <QStandardPaths>

#include "core/controllers/vpnConfigurationController.h"
#include "core/qrCodeUtils.h"
#include "systemController.h"
#include "qrcodegen.hpp"

ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel,
Expand Down Expand Up @@ -50,7 +50,7 @@ void ExportController::generateFullAccessConfig()
compressedConfig = qCompress(compressedConfig, 8);
m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));

m_qrCodes = generateQrCodeImageSeries(compressedConfig);
m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(compressedConfig);
emit exportConfigChanged();
}

Expand Down Expand Up @@ -92,7 +92,7 @@ void ExportController::generateConnectionConfig(const QString &clientName)
compressedConfig = qCompress(compressedConfig, 8);
m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));

m_qrCodes = generateQrCodeImageSeries(compressedConfig);
m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(compressedConfig);
emit exportConfigChanged();
}

Expand Down Expand Up @@ -149,7 +149,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName)
m_config.append(line + "\n");
}

m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8());
m_qrCodes = qrCodeUtuls::generateQrCodeImageSeries(m_config.toUtf8());
emit exportConfigChanged();
}

Expand All @@ -167,8 +167,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
m_config.append(line + "\n");
}

qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
auto qr = qrCodeUtuls::generateQrCode(m_config.toUtf8());
m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));

emit exportConfigChanged();
}
Expand All @@ -187,8 +187,8 @@ void ExportController::generateAwgConfig(const QString &clientName)
m_config.append(line + "\n");
}

qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
auto qr = qrCodeUtuls::generateQrCode(m_config.toUtf8());
m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));

emit exportConfigChanged();
}
Expand Down Expand Up @@ -221,8 +221,8 @@ void ExportController::generateShadowSocksConfig()

m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64();

qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_nativeConfigString.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
auto qr = qrCodeUtuls::generateQrCode(m_nativeConfigString.toUtf8());
m_qrCodes << qrCodeUtuls::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));

emit exportConfigChanged();
}
Expand Down Expand Up @@ -312,32 +312,6 @@ void ExportController::renameClient(const int row, const QString &clientName, co
}
}

QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
{
double k = 850;

quint8 chunksCount = std::ceil(data.size() / k);
QList<QString> chunks;
for (int i = 0; i < data.size(); i = i + k) {
QByteArray chunk;
QDataStream s(&chunk, QIODevice::WriteOnly);
s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k);

QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);

qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
QString svg = QString::fromStdString(toSvgString(qr, 1));
chunks.append(svgToBase64(svg));
}

return chunks;
}

QString ExportController::svgToBase64(const QString &image)
{
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());
}

int ExportController::getQrCodesCount()
{
return m_qrCodes.size();
Expand Down
3 changes: 0 additions & 3 deletions client/ui/controllers/exportController.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ public slots:
void saveFile(const QString &fileName, const QString &data);

private:
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
QString svgToBase64(const QString &image);

int getQrCodesCount();

void clearPreviousConfig();
Expand Down
19 changes: 15 additions & 4 deletions client/ui/controllers/importController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <QStandardPaths>
#include <QUrlQuery>

#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/errorstrings.h"
#include "core/serialization/serialization.h"
#include "systemController.h"
Expand Down Expand Up @@ -45,7 +47,8 @@ namespace

if (config.contains(backupPattern)) {
return ConfigTypes::Backup;
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern)
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)
|| config.contains(amneziaPremiumConfigPattern)
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia;
Expand Down Expand Up @@ -149,8 +152,8 @@ bool ImportController::extractConfigFromData(QString data)

m_configType = checkConfigFormat(config);
if (m_configType == ConfigTypes::Invalid) {
data.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
config.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray ba_uncompressed = qUncompress(ba);
if (!ba_uncompressed.isEmpty()) {
ba = ba_uncompressed;
Expand Down Expand Up @@ -180,6 +183,13 @@ bool ImportController::extractConfigFromData(QString data)
}
case ConfigTypes::Amnezia: {
m_config = QJsonDocument::fromJson(config.toUtf8()).object();

if (apiUtils::isServerFromApi(m_config)) {
auto apiConfig = m_config.value(apiDefs::key::apiConfig).toObject();
apiConfig[apiDefs::key::vpnKey] = data;
m_config[apiDefs::key::apiConfig] = apiConfig;
}

processAmneziaConfig(m_config);
if (!m_config.empty()) {
checkForMaliciousStrings(m_config);
Expand Down Expand Up @@ -680,7 +690,8 @@ void ImportController::processAmneziaConfig(QJsonObject &config)
}

QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object();
jsonConfig[config_key::mtu] = dockerContainer == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
jsonConfig[config_key::mtu] =
dockerContainer == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;

containerConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());

Expand Down
12 changes: 9 additions & 3 deletions client/ui/qml/Components/ShareConnectionDrawer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ DrawerType2 {

property string headerText
property string configContentHeaderText
property string contentVisible
property string shareButtonText: qsTr("Share")
property string copyButtonText: qsTr("Copy")
property bool showSettingsButtonVisible: true

property bool contentVisible

property string configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config")
Expand Down Expand Up @@ -80,7 +84,7 @@ DrawerType2 {
Layout.leftMargin: 16
Layout.rightMargin: 16

text: qsTr("Share")
text: root.shareButtonText
leftImageSource: "qrc:/images/controls/share-2.svg"

clickedFunc: function() {
Expand Down Expand Up @@ -116,7 +120,7 @@ DrawerType2 {
textColor: AmneziaStyle.color.paleGray
borderWidth: 1

text: qsTr("Copy")
text: root.copyButtonText
leftImageSource: "qrc:/images/controls/copy.svg"

Keys.onReturnPressed: { copyConfigTextButton.clicked() }
Expand Down Expand Up @@ -153,6 +157,8 @@ DrawerType2 {
Layout.leftMargin: 16
Layout.rightMargin: 16

visible: root.showSettingsButtonVisible

defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
Expand Down
Loading

0 comments on commit db31642

Please sign in to comment.