Added authentication and TOTP 2FA
This commit is contained in:
parent
6869be8c31
commit
f329fde404
7 changed files with 190 additions and 17 deletions
107
mainwindow.cpp
107
mainwindow.cpp
|
@ -1,14 +1,24 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QFile>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QSettings>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <utils/discordrequests.h>
|
||||||
|
|
||||||
void MainWindow::handleOutput() {
|
void MainWindow::handleOutput() {
|
||||||
QString content = m_box->text();
|
QString content = m_box->text();
|
||||||
m_box->setText("");
|
m_box->setText("");
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case Unauthed:
|
case Unauthed: {
|
||||||
if(!content.startsWith("/login ")) {
|
if(!content.startsWith("/login ")) {
|
||||||
addMessage("You are currently unauthenticated, you can't send messages or use commands!", Error);
|
addMessage("You are currently unauthenticated, you can't send messages or use commands!", Error);
|
||||||
return;
|
return;
|
||||||
|
@ -16,19 +26,55 @@ void MainWindow::handleOutput() {
|
||||||
m_box->setEchoMode(QLineEdit::NoEcho);
|
m_box->setEchoMode(QLineEdit::NoEcho);
|
||||||
m_box->setPlaceholderText("Enter your password");
|
m_box->setPlaceholderText("Enter your password");
|
||||||
state = PendingPassword;
|
state = PendingPassword;
|
||||||
|
m_pendingAuthObject.setEmail(content.replace("/login ", ""));
|
||||||
addMessage(QString("Logging in as <b>%1</b>. Enter your password then press Enter. (you won't be able to see any characters)").arg(content.replace("/login ", "")), System);
|
addMessage(QString("Logging in as <b>%1</b>. 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;
|
break;
|
||||||
case Unauthed:
|
|
||||||
if(!content.startsWith("/login ")) {
|
|
||||||
addMessage("You are currently unauthenticated, you can't send messages or use commands!", Error);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_box->setEchoMode(QLineEdit::NoEcho);
|
case PendingPassword: {
|
||||||
m_box->setPlaceholderText("Enter your password");
|
m_box->setEchoMode(QLineEdit::Normal);
|
||||||
state = PendingPassword;
|
m_box->setPlaceholderText("Message (or /command)");
|
||||||
addMessage(QString("Logging in as <b>%1</b>. Enter your password then press Enter. (you won't be able to see any characters)").arg(content.replace("/login ", "")), System);
|
state = Unauthed;
|
||||||
|
if(content == "cancel") {
|
||||||
|
addMessage("Cancelled.", System);
|
||||||
break;
|
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) {
|
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)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
{
|
{
|
||||||
|
QCoreApplication::setOrganizationName("nin0dev");
|
||||||
|
QCoreApplication::setApplicationName("txtcord");
|
||||||
|
QSettings settings;
|
||||||
QWidget *centralWidget = new QWidget(this);
|
QWidget *centralWidget = new QWidget(this);
|
||||||
m_layout = new QVBoxLayout(centralWidget);
|
m_layout = new QVBoxLayout(centralWidget);
|
||||||
setCentralWidget(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
|
// init chatlog
|
||||||
m_chatlog = new QTextEdit();
|
m_chatlog = new QTextEdit();
|
||||||
m_chatlog->setReadOnly(true);
|
m_chatlog->setReadOnly(true);
|
||||||
m_chatlog->append("<b style=\"color:#00FFE5;\">Welcome to txtcord, the minimal Discord client.</b>");
|
m_chatlog->append("<b style=\"color:#00FFE5;\">Welcome to txtcord, the minimal Discord client.</b>");
|
||||||
|
if(settings.value("token").isNull()) {
|
||||||
addMessage("No accounts are saved. Use the /login [email/phone] command to sign in to your Discord account.", System);
|
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);
|
m_layout->addWidget(m_chatlog);
|
||||||
// init chatbox
|
// init chatbox
|
||||||
QWidget *chatboxWidget = new QWidget();
|
QWidget *chatboxWidget = new QWidget();
|
||||||
|
|
10
mainwindow.h
10
mainwindow.h
|
@ -3,29 +3,35 @@
|
||||||
|
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QNetworkReply>
|
||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <utils/pendingauthobject.h>
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum MessageType {Normal, System, Error};
|
enum MessageType {Normal, System, Error};
|
||||||
enum AppState {Unauthed, Sane, PendingPassword, PendingOTP};
|
enum AppState {Unauthed, NonConnected, Sane, PendingPassword, PendingOTP};
|
||||||
MainWindow(QWidget *parent = nullptr);
|
MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
private slots:
|
private slots:
|
||||||
void handleOutput();
|
void handleOutput();
|
||||||
|
void handleHttpReply(QNetworkReply* reply);
|
||||||
private:
|
private:
|
||||||
AppState state = Unauthed;
|
AppState state = Unauthed;
|
||||||
void addMessage(QString content, MainWindow::MessageType type = Normal);
|
PendingAuthObject m_pendingAuthObject;
|
||||||
QVBoxLayout *m_layout;
|
QVBoxLayout *m_layout;
|
||||||
QTextEdit *m_chatlog;
|
QTextEdit *m_chatlog;
|
||||||
QLineEdit *m_box;
|
QLineEdit *m_box;
|
||||||
QPushButton *m_send;
|
QPushButton *m_send;
|
||||||
|
QNetworkAccessManager *m_netmgr;
|
||||||
|
void addMessage(QString content, MainWindow::MessageType type = Normal);
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|
10
txtcord.pro
10
txtcord.pro
|
@ -1,4 +1,4 @@
|
||||||
QT += core gui
|
QT += core gui network
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
|
@ -10,10 +10,14 @@ CONFIG += c++17
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
mainwindow.cpp
|
mainwindow.cpp \
|
||||||
|
utils/discordrequests.cpp \
|
||||||
|
utils/pendingauthobject.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
mainwindow.h
|
mainwindow.h \
|
||||||
|
utils/discordrequests.h \
|
||||||
|
utils/pendingauthobject.h
|
||||||
|
|
||||||
TRANSLATIONS +=
|
TRANSLATIONS +=
|
||||||
CONFIG += lrelease
|
CONFIG += lrelease
|
||||||
|
|
15
utils/discordrequests.cpp
Normal file
15
utils/discordrequests.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
12
utils/discordrequests.h
Normal file
12
utils/discordrequests.h
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef DISCORDREQUESTS_H
|
||||||
|
#define DISCORDREQUESTS_H
|
||||||
|
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
class DiscordRequests
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DiscordRequests();
|
||||||
|
static QNetworkRequest buildRequest(QString endpoint, bool authenticated = true, bool json = false);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DISCORDREQUESTS_H
|
26
utils/pendingauthobject.cpp
Normal file
26
utils/pendingauthobject.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
23
utils/pendingauthobject.h
Normal file
23
utils/pendingauthobject.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef PENDINGAUTHOBJECT_H
|
||||||
|
#define PENDINGAUTHOBJECT_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in a new issue