aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2021-08-31 00:55:56 +0200
committerPetr Mrázek <peterix@gmail.com>2021-08-31 01:11:52 +0200
commit23442442d86862536acab5a7deca28c1c703b11e (patch)
treea250a43cd5ae52d0f289048dd8ba238a911b17c1 /launcher/minecraft
parent317101430148e3bbc52995aa92d668b8473026d9 (diff)
downloadPrismLauncher-23442442d86862536acab5a7deca28c1c703b11e.tar.gz
PrismLauncher-23442442d86862536acab5a7deca28c1c703b11e.tar.bz2
PrismLauncher-23442442d86862536acab5a7deca28c1c703b11e.zip
GH-3392 fix a bunch of bugs and implement STS error states
Diffstat (limited to 'launcher/minecraft')
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.cpp257
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.h27
-rw-r--r--launcher/minecraft/auth/flows/AuthRequest.cpp121
-rw-r--r--launcher/minecraft/auth/flows/AuthRequest.h65
-rw-r--r--launcher/minecraft/auth/flows/MSAHelper.txt51
5 files changed, 375 insertions, 146 deletions
diff --git a/launcher/minecraft/auth/flows/AuthContext.cpp b/launcher/minecraft/auth/flows/AuthContext.cpp
index 5d7d858d..1203dc5f 100644
--- a/launcher/minecraft/auth/flows/AuthContext.cpp
+++ b/launcher/minecraft/auth/flows/AuthContext.cpp
@@ -16,20 +16,21 @@
#include "AuthContext.h"
#include "katabasis/Globals.h"
-#include "katabasis/Requestor.h"
+#include "AuthRequest.h"
#ifdef EMBED_SECRETS
#include "Secrets.h"
#endif
+#include "Env.h"
+
using OAuth2 = Katabasis::OAuth2;
-using Requestor = Katabasis::Requestor;
+using Requestor = AuthRequest;
using Activity = Katabasis::Activity;
AuthContext::AuthContext(AccountData * data, QObject *parent) :
AccountTask(data, parent)
{
- mgr = new QNetworkAccessManager(this);
}
void AuthContext::beginActivity(Activity activity) {
@@ -63,7 +64,7 @@ void AuthContext::initMSA() {
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
opts.listenerPorts = {28562, 28563, 28564, 28565, 28566};
- m_oauth2 = new OAuth2(opts, m_data->msaToken, this, mgr);
+ m_oauth2 = new OAuth2(opts, m_data->msaToken, this, &ENV.qnam());
m_oauth2->setGrantFlow(Katabasis::OAuth2::GrantFlowDevice);
connect(m_oauth2, &OAuth2::linkingFailed, this, &AuthContext::onOAuthLinkingFailed);
@@ -161,7 +162,7 @@ void AuthContext::doUserAuth() {
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
- auto *requestor = new Requestor(mgr, m_oauth2, this);
+ auto *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onUserAuthDone);
requestor->post(request, xbox_auth_data.toUtf8());
qDebug() << "First layer of XBox auth ... commencing.";
@@ -192,6 +193,13 @@ bool getNumber(QJsonValue value, double & out) {
return true;
}
+bool getNumber(QJsonValue value, int64_t & out) {
+ if(!value.isDouble()) {
+ return false;
+ }
+ out = (int64_t) value.toDouble();
+ return true;
+}
bool getBool(QJsonValue value, bool & out) {
if(!value.isBool()) {
@@ -292,7 +300,6 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, const char
}
void AuthContext::onUserAuthDone(
- int requestId,
QNetworkReply::NetworkError error,
QByteArray replyData,
QList<QNetworkReply::RawHeaderPair> headers
@@ -349,97 +356,65 @@ void AuthContext::doSTSAuthMinecraft() {
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
+ Requestor *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onSTSAuthMinecraftDone);
requestor->post(request, xbox_auth_data.toUtf8());
qDebug() << "Getting Minecraft services STS token...";
}
-void AuthContext::onSTSAuthMinecraftDone(
- int requestId,
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- m_requestsDone ++;
- return;
- }
-
- Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
- qWarning() << "Could not parse authorization response for access to mojang services...";
- m_requestsDone ++;
- return;
- }
+void AuthContext::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) {
+ if(error == QNetworkReply::AuthenticationRequiredError) {
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
+ return;
+ }
- if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
- qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
- qDebug() << replyData;
- m_requestsDone ++;
- return;
+ int64_t errorCode = -1;
+ auto obj = doc.object();
+ if(!getNumber(obj.value("XErr"), errorCode)) {
+ qWarning() << "XErr is not a number";
+ return;
+ }
+ stsErrors.insert(errorCode);
+ stsFailed = true;
}
- m_data->mojangservicesToken = temp;
-
- doMinecraftAuth();
}
-void AuthContext::doSTSAuthGeneric() {
- QString xbox_auth_template = R"XXX(
-{
- "Properties": {
- "SandboxId": "RETAIL",
- "UserTokens": [
- "%1"
- ]
- },
- "RelyingParty": "http://xboxlive.com",
- "TokenType": "JWT"
-}
-)XXX";
- auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token);
-
- QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Accept", "application/json");
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
- connect(requestor, &Requestor::finished, this, &AuthContext::onSTSAuthGenericDone);
- requestor->post(request, xbox_auth_data.toUtf8());
- qDebug() << "Getting generic STS token...";
-}
-void AuthContext::onSTSAuthGenericDone(
- int requestId,
+void AuthContext::onSTSAuthMinecraftDone(
QNetworkReply::NetworkError error,
QByteArray replyData,
QList<QNetworkReply::RawHeaderPair> headers
) {
+#ifndef NDEBUG
+ qDebug() << replyData;
+#endif
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
- m_requestsDone ++;
+ processSTSError(error, replyData, headers);
+ failResult(m_mcAuthSucceeded);
return;
}
Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp, "STSAuthGaneric")) {
- qWarning() << "Could not parse authorization response for access to xbox API...";
- m_requestsDone ++;
+ if(!parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
+ qWarning() << "Could not parse authorization response for access to mojang services...";
+ failResult(m_mcAuthSucceeded);
return;
}
if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
- qDebug() << replyData;
- m_requestsDone ++;
+ failResult(m_mcAuthSucceeded);
return;
}
- m_data->xboxApiToken = temp;
+ m_data->mojangservicesToken = temp;
- doXBoxProfile();
+ doMinecraftAuth();
}
-
void AuthContext::doMinecraftAuth() {
QString mc_auth_template = R"XXX(
{
@@ -451,7 +426,7 @@ void AuthContext::doMinecraftAuth() {
QNetworkRequest request = QNetworkRequest(QUrl("https://api.minecraftservices.com/authentication/login_with_xbox"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
+ Requestor *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onMinecraftAuthDone);
requestor->post(request, data.toUtf8());
qDebug() << "Getting Minecraft access token...";
@@ -498,18 +473,16 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
}
void AuthContext::onMinecraftAuthDone(
- int requestId,
QNetworkReply::NetworkError error,
QByteArray replyData,
QList<QNetworkReply::RawHeaderPair> headers
) {
- m_requestsDone ++;
-
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
#ifndef NDEBUG
qDebug() << replyData;
#endif
+ failResult(m_mcAuthSucceeded);
return;
}
@@ -518,11 +491,67 @@ void AuthContext::onMinecraftAuthDone(
#ifndef NDEBUG
qDebug() << replyData;
#endif
+ failResult(m_mcAuthSucceeded);
return;
}
- m_mcAuthSucceeded = true;
- checkResult();
+ succeedResult(m_mcAuthSucceeded);
+}
+
+void AuthContext::doSTSAuthGeneric() {
+ QString xbox_auth_template = R"XXX(
+{
+ "Properties": {
+ "SandboxId": "RETAIL",
+ "UserTokens": [
+ "%1"
+ ]
+ },
+ "RelyingParty": "http://xboxlive.com",
+ "TokenType": "JWT"
+}
+)XXX";
+ auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token);
+
+ QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ request.setRawHeader("Accept", "application/json");
+ Requestor *requestor = new Requestor(this);
+ connect(requestor, &Requestor::finished, this, &AuthContext::onSTSAuthGenericDone);
+ requestor->post(request, xbox_auth_data.toUtf8());
+ qDebug() << "Getting generic STS token...";
+}
+
+void AuthContext::onSTSAuthGenericDone(
+ QNetworkReply::NetworkError error,
+ QByteArray replyData,
+ QList<QNetworkReply::RawHeaderPair> headers
+) {
+#ifndef NDEBUG
+ qDebug() << replyData;
+#endif
+ if (error != QNetworkReply::NoError) {
+ qWarning() << "Reply error:" << error;
+ processSTSError(error, replyData, headers);
+ failResult(m_xboxProfileSucceeded);
+ return;
+ }
+
+ Katabasis::Token temp;
+ if(!parseXTokenResponse(replyData, temp, "STSAuthGaneric")) {
+ qWarning() << "Could not parse authorization response for access to xbox API...";
+ failResult(m_xboxProfileSucceeded);
+ return;
+ }
+
+ if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
+ qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
+ failResult(m_xboxProfileSucceeded);
+ return;
+ }
+ m_data->xboxApiToken = temp;
+
+ doXBoxProfile();
}
void AuthContext::doXBoxProfile() {
@@ -543,25 +572,23 @@ void AuthContext::doXBoxProfile() {
request.setRawHeader("Accept", "application/json");
request.setRawHeader("x-xbl-contract-version", "3");
request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
+ Requestor *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onXBoxProfileDone);
requestor->get(request);
qDebug() << "Getting Xbox profile...";
}
void AuthContext::onXBoxProfileDone(
- int requestId,
QNetworkReply::NetworkError error,
QByteArray replyData,
QList<QNetworkReply::RawHeaderPair> headers
) {
- m_requestsDone ++;
-
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
#ifndef NDEBUG
qDebug() << replyData;
#endif
+ failResult(m_xboxProfileSucceeded);
return;
}
@@ -569,7 +596,18 @@ void AuthContext::onXBoxProfileDone(
qDebug() << "XBox profile: " << replyData;
#endif
- m_xboxProfileSucceeded = true;
+ succeedResult(m_xboxProfileSucceeded);
+}
+
+void AuthContext::succeedResult(bool& flag) {
+ m_requestsDone ++;
+ flag = true;
+ checkResult();
+}
+
+void AuthContext::failResult(bool& flag) {
+ m_requestsDone ++;
+ flag = false;
checkResult();
}
@@ -584,7 +622,42 @@ void AuthContext::checkResult() {
}
else {
finishActivity();
- changeState(STATE_FAILED_HARD, tr("XBox and/or Mojang authentication steps did not succeed"));
+ if(stsFailed) {
+ if(stsErrors.contains(2148916233)) {
+ changeState(
+ STATE_FAILED_HARD,
+ tr("This Microsoft account does not have an XBox Live profile. Buy the game on %1 first.")
+ .arg("<a href=\"https://www.minecraft.net/en-us/store/minecraft-java-edition\">minecraft.net</a>")
+ );
+ }
+ else if (stsErrors.contains(2148916235)){
+ // NOTE: this is the Grulovia error
+ changeState(
+ STATE_FAILED_HARD,
+ tr("XBox Live is not available in your country. You've been blocked.")
+ );
+ }
+ else if (stsErrors.contains(2148916238)){
+ changeState(
+ STATE_FAILED_HARD,
+ tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
+ .arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/360042649591\">help.minecraft.net</a>")
+ );
+ }
+ else {
+ QStringList errorList;
+ for(auto & error: stsErrors) {
+ errorList.append(QString::number(error));
+ }
+ changeState(
+ STATE_FAILED_HARD,
+ tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorList.join("\n"))
+ );
+ }
+ }
+ else {
+ changeState(STATE_FAILED_HARD, tr("XBox and/or Mojang authentication steps did not succeed"));
+ }
}
}
@@ -678,13 +751,19 @@ void AuthContext::doMinecraftProfile() {
// request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
+ Requestor *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onMinecraftProfileDone);
requestor->get(request);
}
-void AuthContext::onMinecraftProfileDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) {
+void AuthContext::onMinecraftProfileDone(
+ QNetworkReply::NetworkError error,
+ QByteArray data,
+ QList<QNetworkReply::RawHeaderPair> headers
+) {
+#ifndef NDEBUG
qDebug() << data;
+#endif
if (error == QNetworkReply::ContentNotFoundError) {
m_data->minecraftProfile = MinecraftProfile();
finishActivity();
@@ -720,7 +799,7 @@ void AuthContext::doMigrationEligibilityCheck() {
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
+ Requestor *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onMigrationEligibilityCheckDone);
requestor->get(request);
}
@@ -755,7 +834,11 @@ bool parseRolloutResponse(QByteArray & data, bool& result) {
return true;
}
-void AuthContext::onMigrationEligibilityCheckDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) {
+void AuthContext::onMigrationEligibilityCheckDone(
+ QNetworkReply::NetworkError error,
+ QByteArray data,
+ QList<QNetworkReply::RawHeaderPair> headers
+) {
if (error == QNetworkReply::NoError) {
parseRolloutResponse(data, m_data->canMigrateToMSA);
}
@@ -768,12 +851,16 @@ void AuthContext::doGetSkin() {
auto url = QUrl(m_data->minecraftProfile.skin.url);
QNetworkRequest request = QNetworkRequest(url);
- Requestor *requestor = new Requestor(mgr, m_oauth2, this);
+ Requestor *requestor = new Requestor(this);
connect(requestor, &Requestor::finished, this, &AuthContext::onSkinDone);
requestor->get(request);
}
-void AuthContext::onSkinDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair>) {
+void AuthContext::onSkinDone(
+ QNetworkReply::NetworkError error,
+ QByteArray data,
+ QList<QNetworkReply::RawHeaderPair>
+) {
if (error == QNetworkReply::NoError) {
m_data->minecraftProfile.skin.data = data;
}
diff --git a/launcher/minecraft/auth/flows/AuthContext.h b/launcher/minecraft/auth/flows/AuthContext.h
index 7bf69623..dc7552ac 100644
--- a/launcher/minecraft/auth/flows/AuthContext.h
+++ b/launcher/minecraft/auth/flows/AuthContext.h
@@ -3,6 +3,7 @@
#include <QObject>
#include <QList>
#include <QVector>
+#include <QSet>
#include <QNetworkReply>
#include <QImage>
@@ -48,27 +49,31 @@ protected:
void initMojang();
void doUserAuth();
- Q_SLOT void onUserAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onUserAuthDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+
+ void processSTSError(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doSTSAuthMinecraft();
- Q_SLOT void onSTSAuthMinecraftDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onSTSAuthMinecraftDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doMinecraftAuth();
- Q_SLOT void onMinecraftAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onMinecraftAuthDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doSTSAuthGeneric();
- Q_SLOT void onSTSAuthGenericDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onSTSAuthGenericDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doXBoxProfile();
- Q_SLOT void onXBoxProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onXBoxProfileDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doMinecraftProfile();
- Q_SLOT void onMinecraftProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onMinecraftProfileDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doMigrationEligibilityCheck();
- Q_SLOT void onMigrationEligibilityCheckDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onMigrationEligibilityCheckDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
void doGetSkin();
- Q_SLOT void onSkinDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ Q_SLOT void onSkinDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ void failResult(bool & flag);
+ void succeedResult(bool & flag);
void checkResult();
protected:
@@ -83,6 +88,10 @@ protected:
int m_requestsDone = 0;
bool m_xboxProfileSucceeded = false;
bool m_mcAuthSucceeded = false;
+
+ QSet<int64_t> stsErrors;
+ bool stsFailed = false;
+
Katabasis::Activity m_activity = Katabasis::Activity::Idle;
enum class AuthStage {
Initial,
@@ -95,6 +104,4 @@ protected:
} m_stage = AuthStage::Initial;
void setStage(AuthStage stage);
-
- QNetworkAccessManager *mgr = nullptr;
};
diff --git a/launcher/minecraft/auth/flows/AuthRequest.cpp b/launcher/minecraft/auth/flows/AuthRequest.cpp
new file mode 100644
index 00000000..77558fd3
--- /dev/null
+++ b/launcher/minecraft/auth/flows/AuthRequest.cpp
@@ -0,0 +1,121 @@
+#include <cassert>
+
+#include <QDebug>
+#include <QTimer>
+#include <QBuffer>
+#include <QUrlQuery>
+
+#include "AuthRequest.h"
+#include "katabasis/Globals.h"
+#include "Env.h"
+
+AuthRequest::AuthRequest(QObject *parent): QObject(parent) {
+}
+
+AuthRequest::~AuthRequest() {
+}
+
+void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
+ setup(req, QNetworkAccessManager::GetOperation);
+ reply_ = ENV.qnam().get(request_);
+ status_ = Requesting;
+ timedReplies_.add(new Katabasis::Reply(reply_, timeout));
+ connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+ connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
+ connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
+}
+
+void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) {
+ setup(req, QNetworkAccessManager::PostOperation);
+ data_ = data;
+ status_ = Requesting;
+ reply_ = ENV.qnam().post(request_, data_);
+ timedReplies_.add(new Katabasis::Reply(reply_, timeout));
+ connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+ connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
+ connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
+ connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64)));
+}
+
+void AuthRequest::onRequestFinished() {
+ if (status_ == Idle) {
+ return;
+ }
+ if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
+ return;
+ }
+ finish();
+}
+
+void AuthRequest::onRequestError(QNetworkReply::NetworkError error) {
+ qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
+ if (status_ == Idle) {
+ return;
+ }
+ if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
+ return;
+ }
+ qWarning() << "AuthRequest::onRequestError: Error string: " << reply_->errorString();
+ int httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
+ error_ = error;
+
+ // QTimer::singleShot(10, this, SLOT(finish()));
+}
+
+void AuthRequest::onSslErrors(QList<QSslError> errors) {
+ int i = 1;
+ for (auto error : errors) {
+ qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+}
+
+void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total) {
+ if (status_ == Idle) {
+ qWarning() << "AuthRequest::onUploadProgress: No pending request";
+ return;
+ }
+ if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
+ return;
+ }
+ // Restart timeout because request in progress
+ Katabasis::Reply *o2Reply = timedReplies_.find(reply_);
+ if(o2Reply) {
+ o2Reply->start();
+ }
+ emit uploadProgress(uploaded, total);
+}
+
+void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) {
+ request_ = req;
+ operation_ = operation;
+ url_ = req.url();
+
+ QUrl url = url_;
+ request_.setUrl(url);
+
+ if (!verb.isEmpty()) {
+ request_.setRawHeader(Katabasis::HTTP_HTTP_HEADER, verb);
+ }
+
+ status_ = Requesting;
+ error_ = QNetworkReply::NoError;
+}
+
+void AuthRequest::finish() {
+ QByteArray data;
+ if (status_ == Idle) {
+ qWarning() << "AuthRequest::finish: No pending request";
+ return;
+ }
+ data = reply_->readAll();
+ status_ = Idle;
+ timedReplies_.remove(reply_);
+ reply_->disconnect(this);
+ reply_->deleteLater();
+ QList<QNetworkReply::RawHeaderPair> headers = reply_->rawHeaderPairs();
+ emit finished(error_, data, headers);
+}
diff --git a/launcher/minecraft/auth/flows/AuthRequest.h b/launcher/minecraft/auth/flows/AuthRequest.h
new file mode 100644
index 00000000..6a45a0bd
--- /dev/null
+++ b/launcher/minecraft/auth/flows/AuthRequest.h
@@ -0,0 +1,65 @@
+#pragma once
+#include <QObject>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QNetworkAccessManager>
+#include <QUrl>
+#include <QByteArray>
+#include <QHttpMultiPart>
+
+#include "katabasis/Reply.h"
+
+/// Makes authentication requests.
+class AuthRequest: public QObject {
+ Q_OBJECT
+
+public:
+ explicit AuthRequest(QObject *parent = 0);
+ ~AuthRequest();
+
+public slots:
+ void get(const QNetworkRequest &req, int timeout = 60*1000);
+ void post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
+
+
+signals:
+
+ /// Emitted when a request has been completed or failed.
+ void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
+
+ /// Emitted when an upload has progressed.
+ void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
+
+protected slots:
+
+ /// Handle request finished.
+ void onRequestFinished();
+
+ /// Handle request error.
+ void onRequestError(QNetworkReply::NetworkError error);
+
+ /// Handle ssl errors.
+ void onSslErrors(QList<QSslError> errors);
+
+ /// Finish the request, emit finished() signal.
+ void finish();
+
+ /// Handle upload progress.
+ void onUploadProgress(qint64 uploaded, qint64 total);
+
+protected:
+ void setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray());
+
+ enum Status {
+ Idle, Requesting, ReRequesting
+ };
+
+ QNetworkRequest request_;
+ QByteArray data_;
+ QNetworkReply *reply_;
+ Status status_;
+ QNetworkAccessManager::Operation operation_;
+ QUrl url_;
+ Katabasis::ReplyList timedReplies_;
+ QNetworkReply::NetworkError error_;
+};
diff --git a/launcher/minecraft/auth/flows/MSAHelper.txt b/launcher/minecraft/auth/flows/MSAHelper.txt
deleted file mode 100644
index dfaec374..00000000
--- a/launcher/minecraft/auth/flows/MSAHelper.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-class Helper : public QObject {
- Q_OBJECT
-
-public:
- Helper(MSAFlows * context) : QObject(), context_(context), msg_(QString()) {
- QFile tokenCache("usercache.dat");
- if(tokenCache.open(QIODevice::ReadOnly)) {
- context_->resumeFromState(tokenCache.readAll());
- }
- }
-
-public slots:
- void run() {
- connect(context_, &MSAFlows::activityChanged, this, &Helper::onActivityChanged);
- context_->silentSignIn();
- }
-
- void onFailed() {
- qDebug() << "Login failed";
- }
-
- void onActivityChanged(Katabasis::Activity activity) {
- if(activity == Katabasis::Activity::Idle) {
- switch(context_->validity()) {
- case Katabasis::Validity::None: {
- // account is gone, remove it.
- QFile::remove("usercache.dat");
- }
- break;
- case Katabasis::Validity::Assumed: {
- // this is basically a soft-failed refresh. do nothing.
- }
- break;
- case Katabasis::Validity::Certain: {
- // stuff got refreshed / signed in. Save.
- auto data = context_->saveState();
- QSaveFile tokenCache("usercache.dat");
- if(tokenCache.open(QIODevice::WriteOnly)) {
- tokenCache.write(context_->saveState());
- tokenCache.commit();
- }
- }
- break;
- }
- }
- }
-
-private:
- MSAFlows *context_;
- QString msg_;
-};