From f329fde404b2e1fc7e683862528d1b2954357859 Mon Sep 17 00:00:00 2001 From: nin0dev Date: Thu, 15 Aug 2024 12:23:36 -0400 Subject: [PATCH] Added authentication and TOTP 2FA --- mainwindow.cpp | 111 ++++++++++++++++++++++++++++++++---- mainwindow.h | 10 +++- txtcord.pro | 10 +++- utils/discordrequests.cpp | 15 +++++ utils/discordrequests.h | 12 ++++ utils/pendingauthobject.cpp | 26 +++++++++ utils/pendingauthobject.h | 23 ++++++++ 7 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 utils/discordrequests.cpp create mode 100644 utils/discordrequests.h create mode 100644 utils/pendingauthobject.cpp create mode 100644 utils/pendingauthobject.h diff --git a/mainwindow.cpp b/mainwindow.cpp index 7587a57..0f614e6 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,14 +1,24 @@ #include "mainwindow.h" #include +#include #include +#include +#include +#include +#include + +#include +#include #include +#include + void MainWindow::handleOutput() { QString content = m_box->text(); m_box->setText(""); switch(state) { - case Unauthed: + case Unauthed: { if(!content.startsWith("/login ")) { addMessage("You are currently unauthenticated, you can't send messages or use commands!", Error); return; @@ -16,19 +26,55 @@ void MainWindow::handleOutput() { m_box->setEchoMode(QLineEdit::NoEcho); m_box->setPlaceholderText("Enter your password"); state = PendingPassword; + m_pendingAuthObject.setEmail(content.replace("/login ", "")); addMessage(QString("Logging in as %1. Enter your password then press Enter. (you won't be able to see any characters)").arg(content.replace("/login ", "")), System); + addMessage("If you'd like to cancel at any time, type 'cancel'.", System); break; - case Unauthed: - if(!content.startsWith("/login ")) { - addMessage("You are currently unauthenticated, you can't send messages or use commands!", Error); - return; + } + case PendingPassword: { + m_box->setEchoMode(QLineEdit::Normal); + m_box->setPlaceholderText("Message (or /command)"); + state = Unauthed; + if(content == "cancel") { + addMessage("Cancelled.", System); + break; } - m_box->setEchoMode(QLineEdit::NoEcho); - m_box->setPlaceholderText("Enter your password"); - state = PendingPassword; - addMessage(QString("Logging in as %1. Enter your password then press Enter. (you won't be able to see any characters)").arg(content.replace("/login ", "")), System); - break; -} + addMessage("Logging in, please be patient...", System); + QNetworkRequest request = DiscordRequests::buildRequest("auth/login", false, true); + QJsonObject data; + data["undelete"] = true; + data["email"] = m_pendingAuthObject.getEmail(); + data["password"] = content; + m_netmgr->post(request, QJsonDocument(data).toJson()); + break; + } + case PendingOTP: { + if(content == "cancel") { + addMessage("Cancelled.", System); + break; + } + static QRegularExpression re("\\d*"); + if(!re.match(content).hasMatch() || content.length() > 8 || content.length() < 6 || content.length() == 7) { + addMessage("A valid TOTP or backup code is required. Else, enter 'sms' to use SMS auth.", Error); + break; + } + if(content.length() == 6) { + QNetworkRequest request = DiscordRequests::buildRequest("auth/mfa/totp", false, true); + QJsonObject data; + data["ticket"] = m_pendingAuthObject.get2faToken(); + data["code"] = content; + m_netmgr->post(request, QJsonDocument(data).toJson()); + } + if(content.length() == 8) { + QNetworkRequest request = DiscordRequests::buildRequest("auth/mfa/backup", false, true); + QJsonObject data; + data["ticket"] = m_pendingAuthObject.get2faToken(); + data["code"] = content; + m_netmgr->post(request, QJsonDocument(data).toJson()); + } + break; + } + } } void MainWindow::addMessage(QString content, MainWindow::MessageType type) { @@ -43,17 +89,58 @@ void MainWindow::addMessage(QString content, MainWindow::MessageType type) { } } +void MainWindow::handleHttpReply(QNetworkReply* reply) { + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 200) { + addMessage("Something happened", Error); + return; + } + // == AUTH == // + if(data["mfa"].toBool() && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { + m_pendingAuthObject.set2faToken(data["ticket"].toString()); + m_box->setPlaceholderText("Enter your code (or sms)"); + state = PendingOTP; + if(data["sms"].toBool()) { + addMessage("2FA is required to login to this account. Available options: authenticator app, backup codes, SMS auth", System); + addMessage("To use TOTP/backup auth, simply enter your 6-8 digit code. For SMS auth, type 'sms'.", System); + } + else { + addMessage("2FA is required to login to this account. Available options: authenticator app, backup codes", System); + addMessage("Enter your 6-8 digit code.", System); + } + } + if(data["token"].toString() != "") { + QSettings settings; + settings.setValue("token", data["token"].toString()); + addMessage("Auth done!", System); + } + // == END AUTH == // +} + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + QCoreApplication::setOrganizationName("nin0dev"); + QCoreApplication::setApplicationName("txtcord"); + QSettings settings; QWidget *centralWidget = new QWidget(this); m_layout = new QVBoxLayout(centralWidget); setCentralWidget(centralWidget); + // init network manager + m_pendingAuthObject = PendingAuthObject(); + m_netmgr = new QNetworkAccessManager(this); + connect(m_netmgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleHttpReply(QNetworkReply*))); // init chatlog m_chatlog = new QTextEdit(); m_chatlog->setReadOnly(true); m_chatlog->append("Welcome to txtcord, the minimal Discord client."); - addMessage("No accounts are saved. Use the /login [email/phone] command to sign in to your Discord account.", System); + if(settings.value("token").isNull()) { + addMessage("No accounts are saved. Use the /login [email/phone] command to sign in to your Discord account.", System); + } + else { + addMessage("You are logged in, connecting to the Gateway... (soon)", System); + state = NonConnected; + } m_layout->addWidget(m_chatlog); // init chatbox QWidget *chatboxWidget = new QWidget(); diff --git a/mainwindow.h b/mainwindow.h index a8c2e5a..a7dd591 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -3,29 +3,35 @@ #include #include +#include #include #include #include #include #include +#include + class MainWindow : public QMainWindow { Q_OBJECT public: enum MessageType {Normal, System, Error}; - enum AppState {Unauthed, Sane, PendingPassword, PendingOTP}; + enum AppState {Unauthed, NonConnected, Sane, PendingPassword, PendingOTP}; MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void handleOutput(); + void handleHttpReply(QNetworkReply* reply); private: AppState state = Unauthed; - void addMessage(QString content, MainWindow::MessageType type = Normal); + PendingAuthObject m_pendingAuthObject; QVBoxLayout *m_layout; QTextEdit *m_chatlog; QLineEdit *m_box; QPushButton *m_send; + QNetworkAccessManager *m_netmgr; + void addMessage(QString content, MainWindow::MessageType type = Normal); }; #endif // MAINWINDOW_H diff --git a/txtcord.pro b/txtcord.pro index 28deb30..1d20df9 100644 --- a/txtcord.pro +++ b/txtcord.pro @@ -1,4 +1,4 @@ -QT += core gui +QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -10,10 +10,14 @@ CONFIG += c++17 SOURCES += \ main.cpp \ - mainwindow.cpp + mainwindow.cpp \ + utils/discordrequests.cpp \ + utils/pendingauthobject.cpp HEADERS += \ - mainwindow.h + mainwindow.h \ + utils/discordrequests.h \ + utils/pendingauthobject.h TRANSLATIONS += CONFIG += lrelease diff --git a/utils/discordrequests.cpp b/utils/discordrequests.cpp new file mode 100644 index 0000000..c3f95f3 --- /dev/null +++ b/utils/discordrequests.cpp @@ -0,0 +1,15 @@ +#include "discordrequests.h" + +DiscordRequests::DiscordRequests() {} + +QNetworkRequest DiscordRequests::buildRequest(QString endpoint, bool authenticated, bool json) { + QNetworkRequest request(QUrl("https://discord.com/api/v7/" + endpoint)); + // set superprops/agent on Windows Chrome 122 to avoid raising flags + request.setRawHeader("X-Super-Properties", "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiQ2hyb21lIiwiZGV2aWNlIjoiIiwic3lzdGVtX2xvY2FsZSI6ImVuLUdCIiwiYnJvd3Nlcl91c2VyX2FnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEyMi4wLjAuMCBTYWZhcmkvNTM3LjM2IiwiYnJvd3Nlcl92ZXJzaW9uIjoiMTIyLjAuMC4wIiwib3NfdmVyc2lvbiI6IjEwIiwicmVmZXJyZXIiOiIiLCJyZWZlcnJpbmdfZG9tYWluIjoiIiwicmVmZXJyZXJfY3VycmVudCI6IiIsInJlZmVycmluZ19kb21haW5fY3VycmVudCI6IiIsInJlbGVhc2VfY2hhbm5lbCI6InN0YWJsZSIsImNsaWVudF9idWlsZF9udW1iZXIiOjMxODM2MSwiY2xpZW50X2V2ZW50X3NvdXJjZSI6bnVsbH0="); + request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"); + // love ripping off headers from my own session :blobcatcozy: + request.setRawHeader("X-Discord-Locale", "en-GB"); + request.setRawHeader("X-Discord-Timezone", "America/New_York"); + if(json) request.setRawHeader("Content-Type", "application/json"); + return request; +} diff --git a/utils/discordrequests.h b/utils/discordrequests.h new file mode 100644 index 0000000..fd7ab22 --- /dev/null +++ b/utils/discordrequests.h @@ -0,0 +1,12 @@ +#ifndef DISCORDREQUESTS_H +#define DISCORDREQUESTS_H + +#include +class DiscordRequests +{ +public: + DiscordRequests(); + static QNetworkRequest buildRequest(QString endpoint, bool authenticated = true, bool json = false); +}; + +#endif // DISCORDREQUESTS_H diff --git a/utils/pendingauthobject.cpp b/utils/pendingauthobject.cpp new file mode 100644 index 0000000..5cf6720 --- /dev/null +++ b/utils/pendingauthobject.cpp @@ -0,0 +1,26 @@ +#include "pendingauthobject.h" + +PendingAuthObject::PendingAuthObject(QString email, QString password, QString token) { + this->email = email; + this->password = password; + this->twofaToken = token; +} + +QString PendingAuthObject::getEmail() { + return email; +} +QString PendingAuthObject::getPassword() { + return password; +} +QString PendingAuthObject::get2faToken() { + return twofaToken; +} +void PendingAuthObject::setEmail(QString email) { + this->email = email; +} +void PendingAuthObject::setPassword(QString password) { + this->password = password; +} +void PendingAuthObject::set2faToken(QString token) { + this->twofaToken = token; +} diff --git a/utils/pendingauthobject.h b/utils/pendingauthobject.h new file mode 100644 index 0000000..4733725 --- /dev/null +++ b/utils/pendingauthobject.h @@ -0,0 +1,23 @@ +#ifndef PENDINGAUTHOBJECT_H +#define PENDINGAUTHOBJECT_H + +#include + + +class PendingAuthObject +{ +public: + PendingAuthObject(QString email = "", QString password = "", QString token = ""); + QString getEmail(); + QString getPassword(); + QString get2faToken(); + void setEmail(QString email); + void setPassword(QString password); + void set2faToken(QString token); +private: + QString email; + QString password; + QString twofaToken; +}; + +#endif // PENDINGAUTHOBJECT_H