Skip to content

Commit 2e7182b

Browse files
committed
Add OTP management
1 parent 7d96d9e commit 2e7182b

43 files changed

Lines changed: 3091 additions & 104 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3rdparty/QrDecoder.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "QrDecoder.h"
22

33
#include "quirc/quirc.h"
4+
#include <cstring>
45
#include <qdebug.h>
56

67
#include "quirc/quirc_internal.h"
@@ -23,7 +24,17 @@ QVector<QString> QrDecoder::decode(const QImage &image)
2324
return result;
2425
}
2526

26-
if (quirc_resize(m_qr, image.width(), image.height()) < 0)
27+
const QImage grey = image.format() == QImage::Format_Grayscale8
28+
? image
29+
: image.convertToFormat(QImage::Format_Grayscale8);
30+
const int width = grey.width();
31+
const int height = grey.height();
32+
if (width <= 0 || height <= 0)
33+
{
34+
return result;
35+
}
36+
37+
if (quirc_resize(m_qr, width, height) < 0)
2738
{
2839
return result;
2940
}
@@ -33,7 +44,11 @@ QVector<QString> QrDecoder::decode(const QImage &image)
3344
{
3445
return result;
3546
}
36-
std::copy(image.constBits(), image.constBits() + image.width()*image.height(), rawImage);
47+
// QImage pads scanlines to 4 bytes, quirc wants them packed: copying straight through shears it.
48+
for (int y = 0; y < height; ++y)
49+
{
50+
std::memcpy(rawImage + static_cast<size_t>(y) * width, grey.constScanLine(y), width);
51+
}
3752
quirc_end(m_qr);
3853

3954
const int count = quirc_count(m_qr);
@@ -51,7 +66,7 @@ QVector<QString> QrDecoder::decode(const QImage &image)
5166
const quirc_decode_error_t err = quirc_decode(&code, &data);
5267
if (err == QUIRC_SUCCESS)
5368
{
54-
result.append(QLatin1String((const char *)data.payload));
69+
result.append(QString::fromUtf8((const char *)data.payload, data.payload_len));
5570
}
5671
}
5772

CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ set(PROJECT_SOURCES
9393
src/global/HTTPRequestHelper.cpp
9494
src/global/DeviceDetailsHelper.cpp
9595
src/global/CountryHelper.cpp
96+
include/global/OTP.hpp
97+
src/global/OTP.cpp
9698

9799
3rdparty/qrcodegen.cpp
98100
3rdparty/QtExtKeySequenceEdit.cpp
@@ -168,6 +170,8 @@ set(PROJECT_SOURCES
168170
src/ui/widget/StartStopButton.cpp
169171
include/ui/widget/TrayProfileSelector.hpp
170172
src/ui/widget/TrayProfileSelector.cpp
173+
include/ui/widget/TrayOtpCodes.hpp
174+
src/ui/widget/TrayOtpCodes.cpp
171175
include/ui/utils/DataViewHtmlGenerator.h
172176
src/ui/utils/DataViewHtmlGenerator.cpp
173177
include/ui/utils/ProfilesTableModel.h
@@ -179,6 +183,8 @@ set(PROJECT_SOURCES
179183
include/ui/utils/ProfilesTableVerticalHeader.h
180184
src/ui/utils/ProfilesTableVerticalHeader.cpp
181185
include/ui/utils/ProfilesTableFilterHeader.h
186+
include/ui/utils/ScreenQrScanner.h
187+
src/ui/utils/ScreenQrScanner.cpp
182188

183189
include/ui/profile/dialog_edit_profile.h
184190
src/ui/profile/dialog_edit_profile.cpp
@@ -268,6 +274,24 @@ set(PROJECT_SOURCES
268274
include/ui/setting/dialog_vpn_settings.h
269275
include/ui/setting/dialog_vpn_settings.ui
270276

277+
src/ui/setting/dialog_preset_settings.cpp
278+
include/ui/setting/dialog_preset_settings.h
279+
include/ui/setting/dialog_preset_settings.ui
280+
281+
src/ui/setting/dialog_otp_manager.cpp
282+
include/ui/setting/dialog_otp_manager.h
283+
include/ui/setting/dialog_otp_manager.ui
284+
285+
src/ui/setting/dialog_edit_otp.cpp
286+
include/ui/setting/dialog_edit_otp.h
287+
include/ui/setting/dialog_edit_otp.ui
288+
289+
src/ui/setting/OtpItem.cpp
290+
include/ui/setting/OtpItem.h
291+
include/ui/setting/OtpItem.ui
292+
src/ui/setting/OtpListWidget.cpp
293+
include/ui/setting/OtpListWidget.h
294+
271295
src/ui/stats/dialog_traffic_stats.cpp
272296
include/ui/stats/dialog_traffic_stats.h
273297
include/ui/stats/dialog_traffic_stats.ui
@@ -394,22 +418,26 @@ set(PROJECT_SOURCES
394418
include/database/ProfilesRepo.h
395419
include/database/GroupsRepo.h
396420
include/database/RoutesRepo.h
421+
include/database/OtpProfilesRepo.h
397422
include/database/SettingsRepo.h
398423
include/database/TrafficStatsRepo.h
399424

400425
include/database/entities/Profile.h
401426
include/database/entities/Group.h
402427
include/database/entities/RouteProfile.h
403428
include/database/entities/RouteRule.h
429+
include/database/entities/OtpProfile.h
404430

405431
src/database/entities/Profile.cpp
406432
src/database/entities/Group.cpp
407433
src/database/entities/RouteProfile.cpp
408434
src/database/entities/RouteRule.cpp
435+
src/database/entities/OtpProfile.cpp
409436

410437
src/database/ProfilesRepo.cpp
411438
src/database/GroupsRepo.cpp
412439
src/database/RoutesRepo.cpp
440+
src/database/OtpProfilesRepo.cpp
413441
src/database/TrafficStatsRepo.cpp
414442

415443
include/database/DatabaseManager.h

include/database/Database.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ namespace Configs {
3030
// profiles -> groups, groups_order, profiles tables
3131
// routes -> route_profiles, route_rules tables
3232
// settings -> settings table
33+
// otp -> otp_profiles table
3334
// icons -> icons/ folder (handled by the UI layer, not the database)
3435
struct BackupParts {
3536
bool profiles = false;
3637
bool routes = false;
3738
bool settings = false;
39+
bool otp = false;
3840
bool icons = false;
3941

40-
[[nodiscard]] bool anyDb() const { return profiles || routes || settings; }
41-
[[nodiscard]] bool any() const { return profiles || routes || settings || icons; }
42+
[[nodiscard]] bool anyDb() const { return profiles || routes || settings || otp; }
43+
[[nodiscard]] bool any() const { return anyDb() || icons; }
4244
};
4345

4446
// Max bound parameters per statement (SQLite default SQLITE_MAX_VARIABLE_NUMBER is 999).

include/database/DatabaseManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace Configs {
99
class RoutesRepo;
1010
class GroupsRepo;
1111
class ProfilesRepo;
12+
class OtpProfilesRepo;
1213
class TrafficStatsRepo;
1314

1415
void initDB(const std::string& dbPath);
@@ -21,6 +22,7 @@ namespace Configs {
2122
Database statsDb;
2223

2324
static void createEntityIdsTable(Database& db);
25+
static bool entityIdsColumnExists(Database& db, const char* columnName);
2426
// Derive the stats database path (throne_stats.db) as a sibling of the
2527
// main database file.
2628
static std::string deriveStatsDbPath(const std::string& dbPath);
@@ -29,6 +31,7 @@ namespace Configs {
2931
std::unique_ptr<ProfilesRepo> profilesRepo;
3032
std::unique_ptr<GroupsRepo> groupsRepo;
3133
std::unique_ptr<RoutesRepo> routesRepo;
34+
std::unique_ptr<OtpProfilesRepo> otpProfilesRepo;
3235
std::unique_ptr<SettingsRepo> settingsRepo;
3336
std::unique_ptr<TrafficStatsRepo> trafficStatsRepo;
3437

include/database/OtpProfilesRepo.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
3+
#include <QList>
4+
#include <QMutex>
5+
#include <map>
6+
#include <memory>
7+
8+
#include "Database.h"
9+
#include "include/database/entities/OtpProfile.h"
10+
11+
namespace Configs {
12+
class OtpProfilesRepo {
13+
private:
14+
Database &db;
15+
mutable QMutex mutex;
16+
mutable std::map<int, std::weak_ptr<OtpProfile>> identityMap;
17+
18+
void createTables() const;
19+
20+
[[nodiscard]] bool otpProfilesColumnExists(const char *columnName) const;
21+
22+
void saveToDatabase(const OtpProfile *profile, int id) const;
23+
24+
[[nodiscard]] std::shared_ptr<OtpProfile> otpProfileFromRow(SQLite::Statement &stmt) const;
25+
26+
[[nodiscard]] int NewOtpProfileID() const;
27+
28+
public:
29+
explicit OtpProfilesRepo(Database &database);
30+
31+
[[nodiscard]] static std::shared_ptr<OtpProfile> NewOtpProfile();
32+
33+
bool AddOtpProfile(std::shared_ptr<OtpProfile> &profile);
34+
35+
[[nodiscard]] std::shared_ptr<OtpProfile> GetOtpProfile(int id) const;
36+
37+
[[nodiscard]] std::shared_ptr<OtpProfile> GetOtpProfileByName(const QString &name) const;
38+
39+
void DeleteOtpProfile(int id);
40+
41+
void DeleteOtpProfiles(const QList<int> &ids);
42+
43+
[[nodiscard]] QList<std::shared_ptr<OtpProfile>> GetAllOtpProfiles() const;
44+
45+
[[nodiscard]] QList<int> GetAllOtpProfileIds() const;
46+
47+
bool Save(const std::shared_ptr<OtpProfile> &profile);
48+
49+
void UpdateOtpProfilesOrder(const QList<int> &idsInOrder);
50+
};
51+
} // namespace Configs
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
#include <QJsonObject>
4+
#include <QString>
5+
6+
#include "include/global/OTP.hpp"
7+
8+
namespace Configs {
9+
class OtpProfile : public OTP::Entry {
10+
public:
11+
int id = -1;
12+
13+
OtpProfile() = default;
14+
explicit OtpProfile(const OTP::Entry &entry) : OTP::Entry(entry) {}
15+
16+
[[nodiscard]] QString DisplayName() const;
17+
18+
[[nodiscard]] QString CurrentCode() const { return OTP::GenerateCode(*this); }
19+
20+
[[nodiscard]] int SecondsRemaining() const { return OTP::SecondsRemaining(*this); }
21+
22+
[[nodiscard]] QString Validate() const { return OTP::Validate(*this); }
23+
24+
[[nodiscard]] QString ExportToLink() const { return OTP::ExportToLink(*this); }
25+
26+
[[nodiscard]] QJsonObject ExportToJson() const;
27+
28+
bool ParseFromJson(const QJsonObject &object);
29+
};
30+
} // namespace Configs

include/global/OTP.hpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#pragma once
2+
3+
#include <QByteArray>
4+
#include <QList>
5+
#include <QString>
6+
#include <QStringList>
7+
8+
namespace OTP {
9+
enum class Algorithm : int {
10+
SHA1 = 0,
11+
SHA256,
12+
SHA512,
13+
};
14+
15+
enum class Type : int {
16+
TOTP = 0,
17+
HOTP,
18+
};
19+
20+
constexpr int DEFAULT_DIGITS = 6;
21+
constexpr int DEFAULT_PERIOD = 30;
22+
constexpr int MIN_DIGITS = 4;
23+
constexpr int MAX_DIGITS = 10;
24+
constexpr int MIN_PERIOD = 1;
25+
constexpr int MAX_PERIOD = 3600;
26+
27+
struct Entry {
28+
QString name;
29+
QString issuer;
30+
QString secret;
31+
Algorithm algorithm = Algorithm::SHA1;
32+
Type type = Type::TOTP;
33+
int digits = DEFAULT_DIGITS;
34+
int period = DEFAULT_PERIOD;
35+
qint64 counter = 0;
36+
};
37+
38+
QString AlgorithmToString(Algorithm algorithm);
39+
Algorithm AlgorithmFromString(const QString &name, bool *ok = nullptr);
40+
QString TypeToString(Type type);
41+
Type TypeFromString(const QString &name, bool *ok = nullptr);
42+
43+
QByteArray Base32Decode(const QString &input, bool *ok = nullptr);
44+
QString Base32Encode(const QByteArray &input);
45+
46+
QString NormalizeSecret(const QString &secret);
47+
48+
// Empty means usable; a non-empty result is the translated reason it is not.
49+
QString Validate(const Entry &entry);
50+
51+
QString GenerateHOTP(const QByteArray &key, quint64 counter, Algorithm algorithm, int digits);
52+
53+
// `unixTime` < 0 means now.
54+
QString GenerateCode(const Entry &entry, qint64 unixTime = -1);
55+
56+
int SecondsRemaining(const Entry &entry, qint64 unixTime = -1);
57+
58+
QString ExportToLink(const Entry &entry);
59+
bool ParseFromLink(const QString &link, Entry &out);
60+
61+
// Google Authenticator's format: it pins the step to 30 s, so other periods do not survive.
62+
QString ExportToMigrationLink(const QList<Entry> &entries);
63+
bool ParseFromMigrationLink(const QString &link, QList<Entry> &out);
64+
65+
// Takes any of the above or a bare base32 secret, which yields an unnamed entry.
66+
QList<Entry> ParseAny(const QString &text, QStringList *problems = nullptr);
67+
68+
QByteArray ExportToJson(const QList<Entry> &entries);
69+
bool ParseFromJson(const QByteArray &json, QList<Entry> &out);
70+
} // namespace OTP

include/ui/mainwindow.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ namespace Configs_sys {
4949
}
5050

5151
class TrayProfileSelector;
52+
class TrayOtpCodes;
5253
class TestRunner;
5354

5455
namespace Qv2ray::ui { class SyntaxHighlighter; }
@@ -150,6 +151,10 @@ private slots:
150151

151152
void on_menu_vpn_settings_triggered();
152153

154+
void on_menu_preset_settings_triggered();
155+
156+
void on_menu_otp_manager_triggered();
157+
153158
void on_menu_hotkey_settings_triggered();
154159

155160
void on_menu_add_from_input_triggered();
@@ -210,6 +215,8 @@ private slots:
210215
QMenu *trayMenu = nullptr; // tray context menu
211216
QPointer<TrayProfileSelector> traySelector;
212217
void openTraySelector(bool routing);
218+
QPointer<TrayOtpCodes> trayOtpCodes;
219+
void openTrayOtpCodes();
213220
QShortcut *shortcut_esc = new QShortcut(QKeySequence::Cancel, this);
214221
//
215222
QThreadPool *parallelCoreCallPool = new QThreadPool(this);

include/ui/mainwindow.ui

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ QTabBar::tab:selected {
771771
<addaction name="menu_basic_settings"/>
772772
<addaction name="menu_routing_settings"/>
773773
<addaction name="menu_vpn_settings"/>
774+
<addaction name="menu_preset_settings"/>
774775
<addaction name="menu_hotkey_settings"/>
775776
<addaction name="menu_open_config_folder"/>
776777
</widget>
@@ -845,6 +846,8 @@ QTabBar::tab:selected {
845846
<addaction name="actionAuto_Selector"/>
846847
<addaction name="actionSpeedtest_Current"/>
847848
<addaction name="separator"/>
849+
<addaction name="menu_otp_manager"/>
850+
<addaction name="separator"/>
848851
<addaction name="actionRuntime_Stats"/>
849852
<addaction name="actionTraffic_Stats"/>
850853
<addaction name="actionCheck_For_Update"/>
@@ -1174,6 +1177,16 @@ QTabBar::tab:selected {
11741177
<string>Tun Settings</string>
11751178
</property>
11761179
</action>
1180+
<action name="menu_preset_settings">
1181+
<property name="text">
1182+
<string>Preset Settings</string>
1183+
</property>
1184+
</action>
1185+
<action name="menu_otp_manager">
1186+
<property name="text">
1187+
<string>OTP Manager</string>
1188+
</property>
1189+
</action>
11771190
<action name="actionRestart_Program">
11781191
<property name="text">
11791192
<string>Restart Program</string>

0 commit comments

Comments
 (0)