mirror of
https://github.com/uowuo/abaddon.git
synced 2024-11-10 14:10:10 +00:00
Merge pull request #185 from uowuo/remoteauth
Login with QR code/remote auth
This commit is contained in:
commit
337a3d5811
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -67,7 +67,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cond: ${{ matrix.mindeps == true }}
|
cond: ${{ matrix.mindeps == true }}
|
||||||
if_true: |
|
if_true: |
|
||||||
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DENABLE_NOTIFICATION_SOUNDS=OFF
|
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DENABLE_NOTIFICATION_SOUNDS=OFF -DENABLE_QRCODE_LOGIN=OFF
|
||||||
cmake --build build
|
cmake --build build
|
||||||
if_false: |
|
if_false: |
|
||||||
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_CXX_FLAGS="-Wl,--default-image-base-low"
|
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_CXX_FLAGS="-Wl,--default-image-base-low"
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -13,3 +13,6 @@
|
|||||||
[submodule "subprojects/miniaudio"]
|
[submodule "subprojects/miniaudio"]
|
||||||
path = subprojects/miniaudio
|
path = subprojects/miniaudio
|
||||||
url = https://github.com/mackron/miniaudio
|
url = https://github.com/mackron/miniaudio
|
||||||
|
[submodule "subprojects/qrcodegen"]
|
||||||
|
path = subprojects/qrcodegen
|
||||||
|
url = https://github.com/nayuki/QR-Code-generator
|
||||||
|
@ -11,6 +11,7 @@ option(USE_LIBHANDY "Enable features that require libhandy (default)" ON)
|
|||||||
option(ENABLE_VOICE "Enable voice suppport" ON)
|
option(ENABLE_VOICE "Enable voice suppport" ON)
|
||||||
option(USE_KEYCHAIN "Store the token in the keychain (default)" ON)
|
option(USE_KEYCHAIN "Store the token in the keychain (default)" ON)
|
||||||
option(ENABLE_NOTIFICATION_SOUNDS "Enable notification sounds (default)" ON)
|
option(ENABLE_NOTIFICATION_SOUNDS "Enable notification sounds (default)" ON)
|
||||||
|
option(ENABLE_QRCODE_LOGIN "Enable QR code login (default)" ON)
|
||||||
|
|
||||||
find_package(nlohmann_json REQUIRED)
|
find_package(nlohmann_json REQUIRED)
|
||||||
find_package(CURL)
|
find_package(CURL)
|
||||||
@ -61,6 +62,15 @@ target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS})
|
|||||||
target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS})
|
target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS})
|
||||||
target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS})
|
target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
if (ENABLE_QRCODE_LOGIN)
|
||||||
|
add_library(qrcodegen subprojects/qrcodegen/cpp/qrcodegen.hpp subprojects/qrcodegen/cpp/qrcodegen.cpp)
|
||||||
|
target_include_directories(qrcodegen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/qrcodegen/cpp")
|
||||||
|
target_link_libraries(abaddon qrcodegen)
|
||||||
|
|
||||||
|
target_include_directories(abaddon PUBLIC "subprojects/qrcodegen/cpp")
|
||||||
|
target_compile_definitions(abaddon PRIVATE WITH_QRLOGIN)
|
||||||
|
endif ()
|
||||||
|
|
||||||
target_precompile_headers(abaddon PRIVATE <gtkmm.h> src/abaddon.hpp src/util.hpp)
|
target_precompile_headers(abaddon PRIVATE <gtkmm.h> src/abaddon.hpp src/util.hpp)
|
||||||
|
|
||||||
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
|
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
|
||||||
@ -151,12 +161,12 @@ if (ENABLE_VOICE)
|
|||||||
|
|
||||||
target_link_libraries(abaddon ${CMAKE_DL_LIBS})
|
target_link_libraries(abaddon ${CMAKE_DL_LIBS})
|
||||||
|
|
||||||
if(APPLE)
|
if (APPLE)
|
||||||
target_link_libraries(abaddon "-framework CoreFoundation")
|
target_link_libraries(abaddon "-framework CoreFoundation")
|
||||||
target_link_libraries(abaddon "-framework CoreAudio")
|
target_link_libraries(abaddon "-framework CoreAudio")
|
||||||
target_link_libraries(abaddon "-framework AudioToolbox")
|
target_link_libraries(abaddon "-framework AudioToolbox")
|
||||||
endif()
|
endif ()
|
||||||
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (${ENABLE_NOTIFICATION_SOUNDS})
|
if (${ENABLE_NOTIFICATION_SOUNDS})
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "windows/voicewindow.hpp"
|
#include "windows/voicewindow.hpp"
|
||||||
#include "startup.hpp"
|
#include "startup.hpp"
|
||||||
#include "notifications/notifications.hpp"
|
#include "notifications/notifications.hpp"
|
||||||
|
#include "remoteauth/remoteauthdialog.hpp"
|
||||||
|
|
||||||
#ifdef WITH_LIBHANDY
|
#ifdef WITH_LIBHANDY
|
||||||
#include <handy.h>
|
#include <handy.h>
|
||||||
@ -267,6 +268,7 @@ int Abaddon::StartGTK() {
|
|||||||
m_main_window->signal_action_connect().connect(sigc::mem_fun(*this, &Abaddon::ActionConnect));
|
m_main_window->signal_action_connect().connect(sigc::mem_fun(*this, &Abaddon::ActionConnect));
|
||||||
m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect));
|
m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect));
|
||||||
m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken));
|
m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken));
|
||||||
|
m_main_window->signal_action_login_qr().connect(sigc::mem_fun(*this, &Abaddon::ActionLoginQR));
|
||||||
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
|
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
|
||||||
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
|
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
|
||||||
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
|
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
|
||||||
@ -834,6 +836,21 @@ void Abaddon::ActionSetToken() {
|
|||||||
m_main_window->UpdateMenus();
|
m_main_window->UpdateMenus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Abaddon::ActionLoginQR() {
|
||||||
|
#ifdef WITH_QRLOGIN
|
||||||
|
RemoteAuthDialog dlg(*m_main_window);
|
||||||
|
auto response = dlg.run();
|
||||||
|
if (response == Gtk::RESPONSE_OK) {
|
||||||
|
m_discord_token = dlg.GetToken();
|
||||||
|
m_discord.UpdateToken(m_discord_token);
|
||||||
|
m_main_window->UpdateComponents();
|
||||||
|
GetSettings().DiscordToken = m_discord_token;
|
||||||
|
ActionConnect();
|
||||||
|
}
|
||||||
|
m_main_window->UpdateMenus();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) {
|
void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) {
|
||||||
if (!id.IsValid()) {
|
if (!id.IsValid()) {
|
||||||
m_discord.SetReferringChannel(Snowflake::Invalid);
|
m_discord.SetReferringChannel(Snowflake::Invalid);
|
||||||
@ -1142,6 +1159,7 @@ int main(int argc, char **argv) {
|
|||||||
auto log_audio = spdlog::stdout_color_mt("audio");
|
auto log_audio = spdlog::stdout_color_mt("audio");
|
||||||
auto log_voice = spdlog::stdout_color_mt("voice");
|
auto log_voice = spdlog::stdout_color_mt("voice");
|
||||||
auto log_discord = spdlog::stdout_color_mt("discord");
|
auto log_discord = spdlog::stdout_color_mt("discord");
|
||||||
|
auto log_ra = spdlog::stdout_color_mt("remote-auth");
|
||||||
|
|
||||||
Gtk::Main::init_gtkmm_internals(); // why???
|
Gtk::Main::init_gtkmm_internals(); // why???
|
||||||
return Abaddon::Get().StartGTK();
|
return Abaddon::Get().StartGTK();
|
||||||
|
@ -41,6 +41,7 @@ public:
|
|||||||
void ActionConnect();
|
void ActionConnect();
|
||||||
void ActionDisconnect();
|
void ActionDisconnect();
|
||||||
void ActionSetToken();
|
void ActionSetToken();
|
||||||
|
void ActionLoginQR();
|
||||||
void ActionJoinGuildDialog();
|
void ActionJoinGuildDialog();
|
||||||
void ActionChannelOpened(Snowflake id, bool expand_to = true);
|
void ActionChannelOpened(Snowflake id, bool expand_to = true);
|
||||||
void ActionChatInputSubmit(ChatSubmitParams data);
|
void ActionChatInputSubmit(ChatSubmitParams data);
|
||||||
|
@ -1197,6 +1197,27 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DiscordClient::RemoteAuthLogin(const std::string &ticket, const sigc::slot<void(std::optional<std::string>, DiscordError code)> &callback) {
|
||||||
|
http::request req(http::REQUEST_POST, "https://discord.com/api/v9/users/@me/remote-auth/login");
|
||||||
|
req.set_header("Content-Type", "application/json");
|
||||||
|
req.set_user_agent(Abaddon::Get().GetSettings().UserAgent);
|
||||||
|
req.set_body("{\"ticket\":\"" + ticket + "\"}");
|
||||||
|
m_http.Execute(std::move(req), [this, callback](const http::response_type &r) {
|
||||||
|
if (CheckCode(r)) {
|
||||||
|
callback(nlohmann::json::parse(r.text).at("encrypted_token").get<std::string>(), DiscordError::NONE);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const auto j = nlohmann::json::parse(r.text);
|
||||||
|
if (j.contains("captcha_service")) {
|
||||||
|
callback(std::nullopt, DiscordError::CAPTCHA_REQUIRED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (...) {}
|
||||||
|
callback(std::nullopt, GetCodeFromResponse(r));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef WITH_VOICE
|
#ifdef WITH_VOICE
|
||||||
void DiscordClient::ConnectToVoice(Snowflake channel_id) {
|
void DiscordClient::ConnectToVoice(Snowflake channel_id) {
|
||||||
auto channel = GetChannel(channel_id);
|
auto channel = GetChannel(channel_id);
|
||||||
|
@ -184,6 +184,8 @@ public:
|
|||||||
void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot<void(std::optional<VerificationGateInfoObject>)> &callback);
|
void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot<void(std::optional<VerificationGateInfoObject>)> &callback);
|
||||||
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot<void(DiscordError code)> &callback);
|
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot<void(DiscordError code)> &callback);
|
||||||
|
|
||||||
|
void RemoteAuthLogin(const std::string &ticket, const sigc::slot<void(std::optional<std::string>, DiscordError code)> &callback);
|
||||||
|
|
||||||
#ifdef WITH_VOICE
|
#ifdef WITH_VOICE
|
||||||
void ConnectToVoice(Snowflake channel_id);
|
void ConnectToVoice(Snowflake channel_id);
|
||||||
void DisconnectFromVoice();
|
void DisconnectFromVoice();
|
||||||
|
@ -11,6 +11,7 @@ enum class DiscordError {
|
|||||||
RELATIONSHIP_ALREADY_FRIENDS = 80007,
|
RELATIONSHIP_ALREADY_FRIENDS = 80007,
|
||||||
|
|
||||||
NONE = -1,
|
NONE = -1,
|
||||||
|
CAPTCHA_REQUIRED = -2,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr const char *GetDiscordErrorDisplayString(DiscordError error) {
|
constexpr const char *GetDiscordErrorDisplayString(DiscordError error) {
|
||||||
|
@ -26,7 +26,7 @@ void Websocket::StartConnection(const std::string &url) {
|
|||||||
m_websocket->disableAutomaticReconnection();
|
m_websocket->disableAutomaticReconnection();
|
||||||
m_websocket->setUrl(url);
|
m_websocket->setUrl(url);
|
||||||
m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward<decltype(msg)>(msg)); });
|
m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward<decltype(msg)>(msg)); });
|
||||||
m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works
|
m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent }, { "Origin", "https://discord.com" } }); // idk if this actually works
|
||||||
m_websocket->start();
|
m_websocket->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +81,9 @@ void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) {
|
|||||||
case ix::WebSocketMessageType::Message: {
|
case ix::WebSocketMessageType::Message: {
|
||||||
m_signal_message.emit(msg->str);
|
m_signal_message.emit(msg->str);
|
||||||
} break;
|
} break;
|
||||||
|
case ix::WebSocketMessageType::Error: {
|
||||||
|
m_log->error("Websocket error: Status: {} Reason: {}", msg->errorInfo.http_status, msg->errorInfo.reason);
|
||||||
|
} break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
351
src/remoteauth/remoteauthclient.cpp
Normal file
351
src/remoteauth/remoteauthclient.cpp
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
#ifdef WITH_QRLOGIN
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "remoteauthclient.hpp"
|
||||||
|
#include "http.hpp"
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <spdlog/fmt/bin_to_hex.h>
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
RemoteAuthClient::RemoteAuthClient()
|
||||||
|
: m_ws("remote-auth-ws")
|
||||||
|
, m_log(spdlog::get("remote-auth")) {
|
||||||
|
m_ws.signal_open().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketOpen));
|
||||||
|
m_ws.signal_close().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketClose));
|
||||||
|
m_ws.signal_message().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketMessage));
|
||||||
|
|
||||||
|
m_dispatcher.connect(sigc::mem_fun(*this, &RemoteAuthClient::OnDispatch));
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::~RemoteAuthClient() {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::Start() {
|
||||||
|
if (IsConnected()) {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connected = true;
|
||||||
|
m_heartbeat_waiter.revive();
|
||||||
|
m_ws.StartConnection("wss://remote-auth-gateway.discord.gg/?v=2");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::Stop() {
|
||||||
|
if (!IsConnected()) {
|
||||||
|
m_log->warn("Requested stop while not connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connected = false;
|
||||||
|
if (m_timeout_conn) m_timeout_conn.disconnect();
|
||||||
|
m_ws.Stop(1000);
|
||||||
|
m_heartbeat_waiter.kill();
|
||||||
|
if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteAuthClient::IsConnected() const noexcept {
|
||||||
|
return m_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::OnGatewayMessage(const std::string &str) {
|
||||||
|
m_log->trace(str);
|
||||||
|
auto j = nlohmann::json::parse(str);
|
||||||
|
const auto opcode = j.at("op").get<std::string>();
|
||||||
|
if (opcode == "hello") {
|
||||||
|
HandleGatewayHello(j);
|
||||||
|
} else if (opcode == "nonce_proof") {
|
||||||
|
HandleGatewayNonceProof(j);
|
||||||
|
} else if (opcode == "pending_remote_init") {
|
||||||
|
HandleGatewayPendingRemoteInit(j);
|
||||||
|
} else if (opcode == "pending_ticket") {
|
||||||
|
HandleGatewayPendingTicket(j);
|
||||||
|
} else if (opcode == "pending_login") {
|
||||||
|
HandleGatewayPendingLogin(j);
|
||||||
|
} else if (opcode == "cancel") {
|
||||||
|
HandleGatewayCancel(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HandleGatewayHello(const nlohmann::json &j) {
|
||||||
|
const auto timeout_ms = j.at("timeout_ms").get<int>();
|
||||||
|
const auto heartbeat_interval = j.at("heartbeat_interval").get<int>();
|
||||||
|
m_log->debug("Timeout: {}, Heartbeat: {}", timeout_ms, heartbeat_interval);
|
||||||
|
|
||||||
|
m_heartbeat_msec = heartbeat_interval;
|
||||||
|
m_heartbeat_thread = std::thread(&RemoteAuthClient::HeartbeatThread, this);
|
||||||
|
|
||||||
|
m_timeout_conn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnTimeout), timeout_ms);
|
||||||
|
|
||||||
|
Init();
|
||||||
|
|
||||||
|
m_signal_hello.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HandleGatewayNonceProof(const nlohmann::json &j) {
|
||||||
|
m_log->debug("Received encrypted nonce");
|
||||||
|
|
||||||
|
const auto encrypted_nonce = Glib::Base64::decode(j.at("encrypted_nonce").get<std::string>());
|
||||||
|
const auto proof = Decrypt(reinterpret_cast<const unsigned char *>(encrypted_nonce.data()), encrypted_nonce.size());
|
||||||
|
auto proof_encoded = Glib::Base64::encode(std::string(proof.begin(), proof.end()));
|
||||||
|
|
||||||
|
std::replace(proof_encoded.begin(), proof_encoded.end(), '/', '_');
|
||||||
|
std::replace(proof_encoded.begin(), proof_encoded.end(), '+', '-');
|
||||||
|
proof_encoded.erase(std::remove(proof_encoded.begin(), proof_encoded.end(), '='), proof_encoded.end());
|
||||||
|
|
||||||
|
nlohmann::json reply;
|
||||||
|
reply["op"] = "nonce_proof";
|
||||||
|
reply["nonce"] = proof_encoded;
|
||||||
|
m_ws.Send(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HandleGatewayPendingRemoteInit(const nlohmann::json &j) {
|
||||||
|
m_log->debug("Received fingerprint");
|
||||||
|
|
||||||
|
m_signal_fingerprint.emit(j.at("fingerprint").get<std::string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HandleGatewayPendingTicket(const nlohmann::json &j) {
|
||||||
|
const auto encrypted_payload = Glib::Base64::decode(j.at("encrypted_user_payload").get<std::string>());
|
||||||
|
const auto payload = Decrypt(reinterpret_cast<const unsigned char *>(encrypted_payload.data()), encrypted_payload.size());
|
||||||
|
|
||||||
|
m_log->trace("User payload: {}", std::string(payload.begin(), payload.end()));
|
||||||
|
|
||||||
|
const std::vector<Glib::ustring> user_info = Glib::Regex::split_simple(":", std::string(payload.begin(), payload.end()));
|
||||||
|
Snowflake user_id;
|
||||||
|
std::string discriminator;
|
||||||
|
std::string avatar_hash;
|
||||||
|
std::string username;
|
||||||
|
if (user_info.size() >= 4) {
|
||||||
|
user_id = Snowflake(user_info[0]);
|
||||||
|
discriminator = user_info[1];
|
||||||
|
avatar_hash = user_info[2];
|
||||||
|
username = user_info[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
m_signal_pending_ticket.emit(user_id, discriminator, avatar_hash, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HandleGatewayPendingLogin(const nlohmann::json &j) {
|
||||||
|
Abaddon::Get().GetDiscordClient().RemoteAuthLogin(j.at("ticket").get<std::string>(), sigc::mem_fun(*this, &RemoteAuthClient::OnRemoteAuthLoginResponse));
|
||||||
|
m_signal_pending_login.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HandleGatewayCancel(const nlohmann::json &j) {
|
||||||
|
Stop();
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::OnRemoteAuthLoginResponse(const std::optional<std::string> &encrypted_token, DiscordError err) {
|
||||||
|
if (!encrypted_token.has_value()) {
|
||||||
|
m_log->error("Remote auth login failed: {}", static_cast<int>(err));
|
||||||
|
if (err == DiscordError::CAPTCHA_REQUIRED) {
|
||||||
|
m_signal_error.emit("Discord is requiring a captcha. You must use a web browser to log in.");
|
||||||
|
} else {
|
||||||
|
m_signal_error.emit("An error occurred. Try again.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto encrypted = Glib::Base64::decode(*encrypted_token);
|
||||||
|
const auto token = Decrypt(reinterpret_cast<const unsigned char *>(encrypted.data()), encrypted.size());
|
||||||
|
m_signal_token.emit(std::string(token.begin(), token.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::Init() {
|
||||||
|
GenerateKey();
|
||||||
|
const auto key = GetEncodedPublicKey();
|
||||||
|
if (key.empty()) {
|
||||||
|
m_log->error("Something went wrong");
|
||||||
|
// todo disconnect
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json msg;
|
||||||
|
msg["op"] = "init";
|
||||||
|
msg["encoded_public_key"] = key;
|
||||||
|
m_ws.Send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::GenerateKey() {
|
||||||
|
// you javascript people have it so easy
|
||||||
|
// check out this documentation https://www.openssl.org/docs/man1.1.1/man3/PEM_write_bio_PUBKEY.html
|
||||||
|
|
||||||
|
m_pkey_ctx.reset(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr));
|
||||||
|
if (!m_pkey_ctx) {
|
||||||
|
m_log->error("Failed to create RSA context");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_PKEY_keygen_init(m_pkey_ctx.get()) <= 0) {
|
||||||
|
m_log->error("Failed to initialize RSA context");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_PKEY_CTX_set_rsa_keygen_bits(m_pkey_ctx.get(), 2048) <= 0) {
|
||||||
|
m_log->error("Failed to set keygen bits");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_PKEY *pkey_tmp = nullptr;
|
||||||
|
if (EVP_PKEY_keygen(m_pkey_ctx.get(), &pkey_tmp) <= 0) {
|
||||||
|
m_log->error("Failed to generate keypair");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_pkey.reset(pkey_tmp);
|
||||||
|
|
||||||
|
m_dec_ctx.reset(EVP_PKEY_CTX_new(m_pkey.get(), nullptr));
|
||||||
|
if (EVP_PKEY_decrypt_init(m_dec_ctx.get()) <= 0) {
|
||||||
|
m_log->error("Failed to initialize RSA decrypt context");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_PKEY_CTX_set_rsa_padding(m_dec_ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) {
|
||||||
|
m_log->error("EVP_PKEY_CTX_set_rsa_padding failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_PKEY_CTX_set_rsa_oaep_md(m_dec_ctx.get(), EVP_sha256()) <= 0) {
|
||||||
|
m_log->error("EVP_PKEY_CTX_set_rsa_oaep_md failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_PKEY_CTX_set_rsa_mgf1_md(m_dec_ctx.get(), EVP_sha256()) <= 0) {
|
||||||
|
m_log->error("EVP_PKEY_CTX_set_rsa_mgf1_md");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RemoteAuthClient::GetEncodedPublicKey() const {
|
||||||
|
auto bio = BIO_ptr(BIO_new(BIO_s_mem()), BIO_free);
|
||||||
|
if (!bio) {
|
||||||
|
m_log->error("Failed to create BIO");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PEM_write_bio_PUBKEY(bio.get(), m_pkey.get()) <= 0) {
|
||||||
|
m_log->error("Failed to write public key to BIO");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// i think this is freed when the bio is too
|
||||||
|
BUF_MEM *mem = nullptr;
|
||||||
|
if (BIO_get_mem_ptr(bio.get(), &mem) <= 0) {
|
||||||
|
m_log->error("Failed to get BIO mem buf");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mem->data == nullptr || mem->length == 0) {
|
||||||
|
m_log->error("BIO mem buf is null or of zero length");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string pem_pubkey(mem->data, mem->length);
|
||||||
|
// isolate key
|
||||||
|
pem_pubkey.erase(0, pem_pubkey.find("\n") + 1);
|
||||||
|
pem_pubkey.erase(pem_pubkey.rfind("\n-"));
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = pem_pubkey.find("\n")) != std::string::npos) {
|
||||||
|
pem_pubkey.erase(pos, 1);
|
||||||
|
}
|
||||||
|
return pem_pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> RemoteAuthClient::Decrypt(const unsigned char *in, size_t inlen) const {
|
||||||
|
// get length
|
||||||
|
size_t outlen;
|
||||||
|
if (EVP_PKEY_decrypt(m_dec_ctx.get(), nullptr, &outlen, in, inlen) <= 0) {
|
||||||
|
m_log->error("Failed to get length when decrypting");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> ret(outlen);
|
||||||
|
if (EVP_PKEY_decrypt(m_dec_ctx.get(), ret.data(), &outlen, in, inlen) <= 0) {
|
||||||
|
m_log->error("Failed to decrypt");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
ret.resize(outlen);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::OnWebsocketOpen() {
|
||||||
|
m_log->info("Websocket opened");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::OnWebsocketClose(const ix::WebSocketCloseInfo &info) {
|
||||||
|
if (info.remote) {
|
||||||
|
m_log->debug("Websocket closed (remote): {} ({})", info.code, info.reason);
|
||||||
|
if (m_connected) {
|
||||||
|
m_signal_error.emit("Error. Websocket closed (remote): " + std::to_string(info.code) + " (" + info.reason + ")");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_log->debug("Websocket closed (local): {} ({})", info.code, info.reason);
|
||||||
|
if (m_connected) {
|
||||||
|
m_signal_error.emit("Error. Websocket closed (local): " + std::to_string(info.code) + " (" + info.reason + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::OnWebsocketMessage(const std::string &data) {
|
||||||
|
m_dispatch_mutex.lock();
|
||||||
|
m_dispatch_queue.push(data);
|
||||||
|
m_dispatcher.emit();
|
||||||
|
m_dispatch_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::HeartbeatThread() {
|
||||||
|
while (true) {
|
||||||
|
if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break;
|
||||||
|
|
||||||
|
nlohmann::json hb;
|
||||||
|
hb["op"] = "heartbeat";
|
||||||
|
m_ws.Send(hb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthClient::OnDispatch() {
|
||||||
|
m_dispatch_mutex.lock();
|
||||||
|
if (m_dispatch_queue.empty()) {
|
||||||
|
m_dispatch_mutex.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto msg = std::move(m_dispatch_queue.front());
|
||||||
|
m_dispatch_queue.pop();
|
||||||
|
m_dispatch_mutex.unlock();
|
||||||
|
OnGatewayMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteAuthClient::OnTimeout() {
|
||||||
|
m_log->trace("Socket timeout");
|
||||||
|
Stop();
|
||||||
|
Start();
|
||||||
|
return false; // disconnect
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::type_signal_hello RemoteAuthClient::signal_hello() {
|
||||||
|
return m_signal_hello;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::type_signal_fingerprint RemoteAuthClient::signal_fingerprint() {
|
||||||
|
return m_signal_fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::type_signal_pending_ticket RemoteAuthClient::signal_pending_ticket() {
|
||||||
|
return m_signal_pending_ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::type_signal_pending_login RemoteAuthClient::signal_pending_login() {
|
||||||
|
return m_signal_pending_login;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::type_signal_token RemoteAuthClient::signal_token() {
|
||||||
|
return m_signal_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAuthClient::type_signal_error RemoteAuthClient::signal_error() {
|
||||||
|
return m_signal_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
95
src/remoteauth/remoteauthclient.hpp
Normal file
95
src/remoteauth/remoteauthclient.hpp
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef WITH_QRLOGIN
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <queue>
|
||||||
|
#include <spdlog/logger.h>
|
||||||
|
#include "ssl.hpp"
|
||||||
|
#include "discord/waiter.hpp"
|
||||||
|
#include "discord/websocket.hpp"
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
class RemoteAuthClient {
|
||||||
|
public:
|
||||||
|
RemoteAuthClient();
|
||||||
|
~RemoteAuthClient();
|
||||||
|
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsConnected() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OnGatewayMessage(const std::string &str);
|
||||||
|
void HandleGatewayHello(const nlohmann::json &j);
|
||||||
|
void HandleGatewayNonceProof(const nlohmann::json &j);
|
||||||
|
void HandleGatewayPendingRemoteInit(const nlohmann::json &j);
|
||||||
|
void HandleGatewayPendingTicket(const nlohmann::json &j);
|
||||||
|
void HandleGatewayPendingLogin(const nlohmann::json &j);
|
||||||
|
void HandleGatewayCancel(const nlohmann::json &j);
|
||||||
|
|
||||||
|
void OnRemoteAuthLoginResponse(const std::optional<std::string> &encrypted_token, DiscordError err);
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
void GenerateKey();
|
||||||
|
std::string GetEncodedPublicKey() const;
|
||||||
|
|
||||||
|
std::vector<uint8_t> Decrypt(const unsigned char *in, size_t inlen) const;
|
||||||
|
|
||||||
|
void OnWebsocketOpen();
|
||||||
|
void OnWebsocketClose(const ix::WebSocketCloseInfo &info);
|
||||||
|
void OnWebsocketMessage(const std::string &str);
|
||||||
|
|
||||||
|
void HeartbeatThread();
|
||||||
|
|
||||||
|
int m_heartbeat_msec;
|
||||||
|
Waiter m_heartbeat_waiter;
|
||||||
|
std::thread m_heartbeat_thread;
|
||||||
|
|
||||||
|
Glib::Dispatcher m_dispatcher;
|
||||||
|
std::queue<std::string> m_dispatch_queue;
|
||||||
|
std::mutex m_dispatch_mutex;
|
||||||
|
|
||||||
|
void OnDispatch();
|
||||||
|
|
||||||
|
bool OnTimeout();
|
||||||
|
sigc::connection m_timeout_conn;
|
||||||
|
|
||||||
|
Websocket m_ws;
|
||||||
|
bool m_connected = false;
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> m_log;
|
||||||
|
|
||||||
|
EVP_PKEY_CTX_ptr m_pkey_ctx;
|
||||||
|
EVP_PKEY_CTX_ptr m_dec_ctx;
|
||||||
|
EVP_PKEY_ptr m_pkey;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using type_signal_hello = sigc::signal<void()>;
|
||||||
|
using type_signal_fingerprint = sigc::signal<void(std::string)>;
|
||||||
|
using type_signal_pending_ticket = sigc::signal<void(Snowflake, std::string, std::string, std::string)>;
|
||||||
|
using type_signal_pending_login = sigc::signal<void()>;
|
||||||
|
using type_signal_token = sigc::signal<void(std::string)>;
|
||||||
|
using type_signal_error = sigc::signal<void(std::string)>;
|
||||||
|
type_signal_hello signal_hello();
|
||||||
|
type_signal_fingerprint signal_fingerprint();
|
||||||
|
type_signal_pending_ticket signal_pending_ticket();
|
||||||
|
type_signal_pending_login signal_pending_login();
|
||||||
|
type_signal_token signal_token();
|
||||||
|
type_signal_error signal_error();
|
||||||
|
|
||||||
|
private:
|
||||||
|
type_signal_hello m_signal_hello;
|
||||||
|
type_signal_fingerprint m_signal_fingerprint;
|
||||||
|
type_signal_pending_ticket m_signal_pending_ticket;
|
||||||
|
type_signal_pending_login m_signal_pending_login;
|
||||||
|
type_signal_token m_signal_token;
|
||||||
|
type_signal_error m_signal_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
132
src/remoteauth/remoteauthdialog.cpp
Normal file
132
src/remoteauth/remoteauthdialog.cpp
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#ifdef WITH_QRLOGIN
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "remoteauthdialog.hpp"
|
||||||
|
#include <qrcodegen.hpp>
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
RemoteAuthDialog::RemoteAuthDialog(Gtk::Window &parent)
|
||||||
|
: Gtk::Dialog("Login with QR Code", parent, true)
|
||||||
|
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||||
|
, m_ok("OK")
|
||||||
|
, m_cancel("Cancel")
|
||||||
|
, m_bbox(Gtk::ORIENTATION_HORIZONTAL) {
|
||||||
|
set_default_size(300, 50);
|
||||||
|
get_style_context()->add_class("app-window");
|
||||||
|
get_style_context()->add_class("app-popup");
|
||||||
|
|
||||||
|
m_ok.signal_clicked().connect([&]() {
|
||||||
|
response(Gtk::RESPONSE_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_cancel.signal_clicked().connect([&]() {
|
||||||
|
response(Gtk::RESPONSE_CANCEL);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK);
|
||||||
|
m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK);
|
||||||
|
m_bbox.set_layout(Gtk::BUTTONBOX_END);
|
||||||
|
|
||||||
|
m_ra.signal_hello().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnHello));
|
||||||
|
m_ra.signal_fingerprint().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnFingerprint));
|
||||||
|
m_ra.signal_pending_ticket().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnPendingTicket));
|
||||||
|
m_ra.signal_pending_login().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnPendingLogin));
|
||||||
|
m_ra.signal_token().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnToken));
|
||||||
|
m_ra.signal_error().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnError));
|
||||||
|
|
||||||
|
m_ra.Start();
|
||||||
|
|
||||||
|
m_image.set_size_request(256, 256);
|
||||||
|
|
||||||
|
m_status.set_text("Connecting...");
|
||||||
|
m_status.set_hexpand(true);
|
||||||
|
m_status.set_halign(Gtk::ALIGN_CENTER);
|
||||||
|
|
||||||
|
m_layout.add(m_image);
|
||||||
|
m_layout.add(m_status);
|
||||||
|
m_layout.add(m_bbox);
|
||||||
|
get_content_area()->add(m_layout);
|
||||||
|
|
||||||
|
show_all_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RemoteAuthDialog::GetToken() {
|
||||||
|
return m_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthDialog::OnHello() {
|
||||||
|
m_status.set_text("Handshaking...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) {
|
||||||
|
m_status.set_text("Waiting for mobile device...");
|
||||||
|
|
||||||
|
const auto url = "https://discord.com/ra/" + fingerprint;
|
||||||
|
|
||||||
|
const auto level = qrcodegen::QrCode::Ecc::QUARTILE;
|
||||||
|
const auto qr = qrcodegen::QrCode::encodeText(url.c_str(), level);
|
||||||
|
|
||||||
|
int size = qr.getSize();
|
||||||
|
const int border = 4;
|
||||||
|
|
||||||
|
const auto module_set = "192 0 255";
|
||||||
|
const auto module_clr = "255 255 255";
|
||||||
|
|
||||||
|
std::ostringstream sb;
|
||||||
|
sb << "P3\n";
|
||||||
|
sb << size + border * 2 << " " << size + border * 2 << " 255\n";
|
||||||
|
for (int y = -border; y < size + border; y++) {
|
||||||
|
for (int x = -border; x < size + border; x++) {
|
||||||
|
if (qr.getModule(x, y)) {
|
||||||
|
sb << module_set << "\n";
|
||||||
|
} else {
|
||||||
|
sb << module_clr << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto img = sb.str();
|
||||||
|
|
||||||
|
auto loader = Gdk::PixbufLoader::create();
|
||||||
|
loader->write(reinterpret_cast<const guint8 *>(img.data()), img.size());
|
||||||
|
loader->close();
|
||||||
|
const auto pb = loader->get_pixbuf()->scale_simple(256, 256, Gdk::INTERP_NEAREST);
|
||||||
|
|
||||||
|
m_image.property_pixbuf() = pb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthDialog::OnPendingTicket(Snowflake user_id, const std::string &discriminator, const std::string &avatar_hash, const std::string &username) {
|
||||||
|
Glib::ustring name = username;
|
||||||
|
if (discriminator != "0") {
|
||||||
|
name += "#" + discriminator;
|
||||||
|
}
|
||||||
|
m_status.set_text("Waiting for confirmation... (" + name + ")");
|
||||||
|
|
||||||
|
if (!avatar_hash.empty()) {
|
||||||
|
const auto url = "https://cdn.discordapp.com/avatars/" + std::to_string(user_id) + "/" + avatar_hash + ".png?size=256";
|
||||||
|
const auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||||
|
m_image.property_pixbuf() = pb->scale_simple(256, 256, Gdk::INTERP_BILINEAR);
|
||||||
|
};
|
||||||
|
Abaddon::Get().GetImageManager().LoadFromURL(url, sigc::track_obj(cb, *this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthDialog::OnPendingLogin() {
|
||||||
|
m_status.set_text("Logging in!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthDialog::OnToken(const std::string &token) {
|
||||||
|
m_token = token;
|
||||||
|
m_ra.Stop();
|
||||||
|
response(Gtk::RESPONSE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteAuthDialog::OnError(const std::string &error) {
|
||||||
|
m_ra.Stop();
|
||||||
|
Abaddon::Get().ShowConfirm(error, dynamic_cast<Gtk::Window *>(get_toplevel()));
|
||||||
|
response(Gtk::RESPONSE_CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
38
src/remoteauth/remoteauthdialog.hpp
Normal file
38
src/remoteauth/remoteauthdialog.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef WITH_QRLOGIN
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include <gtkmm/dialog.h>
|
||||||
|
#include "remoteauthclient.hpp"
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
class RemoteAuthDialog : public Gtk::Dialog {
|
||||||
|
public:
|
||||||
|
RemoteAuthDialog(Gtk::Window &parent);
|
||||||
|
std::string GetToken();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Gtk::Image m_image;
|
||||||
|
Gtk::Label m_status;
|
||||||
|
Gtk::Box m_layout;
|
||||||
|
Gtk::Button m_ok;
|
||||||
|
Gtk::Button m_cancel;
|
||||||
|
Gtk::ButtonBox m_bbox;
|
||||||
|
|
||||||
|
private:
|
||||||
|
RemoteAuthClient m_ra;
|
||||||
|
|
||||||
|
void OnHello();
|
||||||
|
void OnFingerprint(const std::string &fingerprint);
|
||||||
|
void OnPendingTicket(Snowflake user_id, const std::string &discriminator, const std::string &avatar_hash, const std::string &username);
|
||||||
|
void OnPendingLogin();
|
||||||
|
void OnToken(const std::string &token);
|
||||||
|
void OnError(const std::string &error);
|
||||||
|
|
||||||
|
std::string m_token;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
31
src/remoteauth/ssl.hpp
Normal file
31
src/remoteauth/ssl.hpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
struct EVP_PKEY_CTX_deleter {
|
||||||
|
void operator()(EVP_PKEY_CTX *ptr) const {
|
||||||
|
EVP_PKEY_CTX_free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EVP_PKEY_deleter {
|
||||||
|
void operator()(EVP_PKEY *ptr) const {
|
||||||
|
EVP_PKEY_free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EVP_MD_CTX_deleter {
|
||||||
|
void operator()(EVP_MD_CTX *ptr) const {
|
||||||
|
EVP_MD_CTX_free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using EVP_PKEY_CTX_ptr = std::unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_deleter>;
|
||||||
|
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, EVP_PKEY_deleter>;
|
||||||
|
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_deleter>;
|
||||||
|
using BIO_ptr = std::unique_ptr<BIO, decltype(&BIO_free)>;
|
||||||
|
using BUF_MEM_ptr = std::unique_ptr<BUF_MEM, decltype(&BUF_MEM_free)>;
|
@ -206,6 +206,9 @@ void MainWindow::OnDiscordSubmenuPopup() {
|
|||||||
m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active);
|
m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active);
|
||||||
m_menu_discord_disconnect.set_sensitive(discord_active);
|
m_menu_discord_disconnect.set_sensitive(discord_active);
|
||||||
m_menu_discord_set_token.set_sensitive(!discord_active);
|
m_menu_discord_set_token.set_sensitive(!discord_active);
|
||||||
|
#ifdef WITH_QRLOGIN
|
||||||
|
m_menu_discord_login_qr.set_sensitive(!discord_active);
|
||||||
|
#endif
|
||||||
m_menu_discord_set_status.set_sensitive(discord_active);
|
m_menu_discord_set_status.set_sensitive(discord_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,12 +250,18 @@ void MainWindow::SetupMenu() {
|
|||||||
m_menu_discord_disconnect.set_label("Disconnect");
|
m_menu_discord_disconnect.set_label("Disconnect");
|
||||||
m_menu_discord_disconnect.set_sensitive(false);
|
m_menu_discord_disconnect.set_sensitive(false);
|
||||||
m_menu_discord_set_token.set_label("Set Token");
|
m_menu_discord_set_token.set_label("Set Token");
|
||||||
|
m_menu_discord_login_qr.set_label("Login with QR Code");
|
||||||
|
#ifndef WITH_QRLOGIN
|
||||||
|
m_menu_discord_login_qr.set_sensitive(false);
|
||||||
|
m_menu_discord_login_qr.set_tooltip_text("Not compiled with support");
|
||||||
|
#endif
|
||||||
m_menu_discord_set_status.set_label("Set Status");
|
m_menu_discord_set_status.set_label("Set Status");
|
||||||
m_menu_discord_set_status.set_sensitive(false);
|
m_menu_discord_set_status.set_sensitive(false);
|
||||||
m_menu_discord_add_recipient.set_label("Add user to DM");
|
m_menu_discord_add_recipient.set_label("Add user to DM");
|
||||||
m_menu_discord_sub.append(m_menu_discord_connect);
|
m_menu_discord_sub.append(m_menu_discord_connect);
|
||||||
m_menu_discord_sub.append(m_menu_discord_disconnect);
|
m_menu_discord_sub.append(m_menu_discord_disconnect);
|
||||||
m_menu_discord_sub.append(m_menu_discord_set_token);
|
m_menu_discord_sub.append(m_menu_discord_set_token);
|
||||||
|
m_menu_discord_sub.append(m_menu_discord_login_qr);
|
||||||
m_menu_discord_sub.append(m_menu_discord_set_status);
|
m_menu_discord_sub.append(m_menu_discord_set_status);
|
||||||
m_menu_discord_sub.append(m_menu_discord_add_recipient);
|
m_menu_discord_sub.append(m_menu_discord_add_recipient);
|
||||||
m_menu_discord.set_submenu(m_menu_discord_sub);
|
m_menu_discord.set_submenu(m_menu_discord_sub);
|
||||||
@ -331,6 +340,10 @@ void MainWindow::SetupMenu() {
|
|||||||
m_signal_action_set_token.emit();
|
m_signal_action_set_token.emit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_menu_discord_login_qr.signal_activate().connect([this] {
|
||||||
|
m_signal_action_login_qr.emit();
|
||||||
|
});
|
||||||
|
|
||||||
m_menu_file_reload_css.signal_activate().connect([this] {
|
m_menu_file_reload_css.signal_activate().connect([this] {
|
||||||
m_signal_action_reload_css.emit();
|
m_signal_action_reload_css.emit();
|
||||||
});
|
});
|
||||||
@ -421,6 +434,10 @@ MainWindow::type_signal_action_set_token MainWindow::signal_action_set_token() {
|
|||||||
return m_signal_action_set_token;
|
return m_signal_action_set_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MainWindow::type_signal_action_login_qr MainWindow::signal_action_login_qr() {
|
||||||
|
return m_signal_action_login_qr;
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css() {
|
MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css() {
|
||||||
return m_signal_action_reload_css;
|
return m_signal_action_reload_css;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ private:
|
|||||||
Gtk::MenuItem m_menu_discord_connect;
|
Gtk::MenuItem m_menu_discord_connect;
|
||||||
Gtk::MenuItem m_menu_discord_disconnect;
|
Gtk::MenuItem m_menu_discord_disconnect;
|
||||||
Gtk::MenuItem m_menu_discord_set_token;
|
Gtk::MenuItem m_menu_discord_set_token;
|
||||||
|
Gtk::MenuItem m_menu_discord_login_qr;
|
||||||
Gtk::MenuItem m_menu_discord_set_status;
|
Gtk::MenuItem m_menu_discord_set_status;
|
||||||
Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day
|
Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day
|
||||||
void OnDiscordSubmenuPopup();
|
void OnDiscordSubmenuPopup();
|
||||||
@ -103,6 +104,7 @@ public:
|
|||||||
typedef sigc::signal<void> type_signal_action_connect;
|
typedef sigc::signal<void> type_signal_action_connect;
|
||||||
typedef sigc::signal<void> type_signal_action_disconnect;
|
typedef sigc::signal<void> type_signal_action_disconnect;
|
||||||
typedef sigc::signal<void> type_signal_action_set_token;
|
typedef sigc::signal<void> type_signal_action_set_token;
|
||||||
|
typedef sigc::signal<void> type_signal_action_login_qr;
|
||||||
typedef sigc::signal<void> type_signal_action_reload_css;
|
typedef sigc::signal<void> type_signal_action_reload_css;
|
||||||
typedef sigc::signal<void> type_signal_action_set_status;
|
typedef sigc::signal<void> type_signal_action_set_status;
|
||||||
// this should probably be removed
|
// this should probably be removed
|
||||||
@ -113,6 +115,7 @@ public:
|
|||||||
type_signal_action_connect signal_action_connect();
|
type_signal_action_connect signal_action_connect();
|
||||||
type_signal_action_disconnect signal_action_disconnect();
|
type_signal_action_disconnect signal_action_disconnect();
|
||||||
type_signal_action_set_token signal_action_set_token();
|
type_signal_action_set_token signal_action_set_token();
|
||||||
|
type_signal_action_login_qr signal_action_login_qr();
|
||||||
type_signal_action_reload_css signal_action_reload_css();
|
type_signal_action_reload_css signal_action_reload_css();
|
||||||
type_signal_action_set_status signal_action_set_status();
|
type_signal_action_set_status signal_action_set_status();
|
||||||
type_signal_action_add_recipient signal_action_add_recipient();
|
type_signal_action_add_recipient signal_action_add_recipient();
|
||||||
@ -123,6 +126,7 @@ private:
|
|||||||
type_signal_action_connect m_signal_action_connect;
|
type_signal_action_connect m_signal_action_connect;
|
||||||
type_signal_action_disconnect m_signal_action_disconnect;
|
type_signal_action_disconnect m_signal_action_disconnect;
|
||||||
type_signal_action_set_token m_signal_action_set_token;
|
type_signal_action_set_token m_signal_action_set_token;
|
||||||
|
type_signal_action_login_qr m_signal_action_login_qr;
|
||||||
type_signal_action_reload_css m_signal_action_reload_css;
|
type_signal_action_reload_css m_signal_action_reload_css;
|
||||||
type_signal_action_set_status m_signal_action_set_status;
|
type_signal_action_set_status m_signal_action_set_status;
|
||||||
type_signal_action_add_recipient m_signal_action_add_recipient;
|
type_signal_action_add_recipient m_signal_action_add_recipient;
|
||||||
|
1
subprojects/qrcodegen
Submodule
1
subprojects/qrcodegen
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 22fac31bdf81da68730c177c0e931c93234d2a30
|
Loading…
Reference in New Issue
Block a user