GH-3392 fix a bunch of bugs and implement STS error states
This commit is contained in:
parent
3171014301
commit
23442442d8
@ -215,6 +215,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/MinecraftAccount.cpp
|
minecraft/auth/MinecraftAccount.cpp
|
||||||
minecraft/auth/flows/AuthContext.h
|
minecraft/auth/flows/AuthContext.h
|
||||||
minecraft/auth/flows/AuthContext.cpp
|
minecraft/auth/flows/AuthContext.cpp
|
||||||
|
minecraft/auth/flows/AuthRequest.h
|
||||||
|
minecraft/auth/flows/AuthRequest.cpp
|
||||||
|
|
||||||
minecraft/auth/flows/MSAInteractive.h
|
minecraft/auth/flows/MSAInteractive.h
|
||||||
minecraft/auth/flows/MSAInteractive.cpp
|
minecraft/auth/flows/MSAInteractive.cpp
|
||||||
|
@ -16,20 +16,21 @@
|
|||||||
|
|
||||||
#include "AuthContext.h"
|
#include "AuthContext.h"
|
||||||
#include "katabasis/Globals.h"
|
#include "katabasis/Globals.h"
|
||||||
#include "katabasis/Requestor.h"
|
#include "AuthRequest.h"
|
||||||
|
|
||||||
#ifdef EMBED_SECRETS
|
#ifdef EMBED_SECRETS
|
||||||
#include "Secrets.h"
|
#include "Secrets.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "Env.h"
|
||||||
|
|
||||||
using OAuth2 = Katabasis::OAuth2;
|
using OAuth2 = Katabasis::OAuth2;
|
||||||
using Requestor = Katabasis::Requestor;
|
using Requestor = AuthRequest;
|
||||||
using Activity = Katabasis::Activity;
|
using Activity = Katabasis::Activity;
|
||||||
|
|
||||||
AuthContext::AuthContext(AccountData * data, QObject *parent) :
|
AuthContext::AuthContext(AccountData * data, QObject *parent) :
|
||||||
AccountTask(data, parent)
|
AccountTask(data, parent)
|
||||||
{
|
{
|
||||||
mgr = new QNetworkAccessManager(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthContext::beginActivity(Activity activity) {
|
void AuthContext::beginActivity(Activity activity) {
|
||||||
@ -63,7 +64,7 @@ void AuthContext::initMSA() {
|
|||||||
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
|
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
|
||||||
opts.listenerPorts = {28562, 28563, 28564, 28565, 28566};
|
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);
|
m_oauth2->setGrantFlow(Katabasis::OAuth2::GrantFlowDevice);
|
||||||
|
|
||||||
connect(m_oauth2, &OAuth2::linkingFailed, this, &AuthContext::onOAuthLinkingFailed);
|
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"));
|
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Accept", "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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onUserAuthDone);
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
requestor->post(request, xbox_auth_data.toUtf8());
|
||||||
qDebug() << "First layer of XBox auth ... commencing.";
|
qDebug() << "First layer of XBox auth ... commencing.";
|
||||||
@ -192,6 +193,13 @@ bool getNumber(QJsonValue value, double & out) {
|
|||||||
return true;
|
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) {
|
bool getBool(QJsonValue value, bool & out) {
|
||||||
if(!value.isBool()) {
|
if(!value.isBool()) {
|
||||||
@ -292,7 +300,6 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, const char
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AuthContext::onUserAuthDone(
|
void AuthContext::onUserAuthDone(
|
||||||
int requestId,
|
|
||||||
QNetworkReply::NetworkError error,
|
QNetworkReply::NetworkError error,
|
||||||
QByteArray replyData,
|
QByteArray replyData,
|
||||||
QList<QNetworkReply::RawHeaderPair> headers
|
QList<QNetworkReply::RawHeaderPair> headers
|
||||||
@ -349,35 +356,58 @@ void AuthContext::doSTSAuthMinecraft() {
|
|||||||
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
|
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Accept", "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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onSTSAuthMinecraftDone);
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
requestor->post(request, xbox_auth_data.toUtf8());
|
||||||
qDebug() << "Getting Minecraft services STS token...";
|
qDebug() << "Getting Minecraft services STS token...";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AuthContext::onSTSAuthMinecraftDone(
|
void AuthContext::onSTSAuthMinecraftDone(
|
||||||
int requestId,
|
|
||||||
QNetworkReply::NetworkError error,
|
QNetworkReply::NetworkError error,
|
||||||
QByteArray replyData,
|
QByteArray replyData,
|
||||||
QList<QNetworkReply::RawHeaderPair> headers
|
QList<QNetworkReply::RawHeaderPair> headers
|
||||||
) {
|
) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
qDebug() << replyData;
|
||||||
|
#endif
|
||||||
if (error != QNetworkReply::NoError) {
|
if (error != QNetworkReply::NoError) {
|
||||||
qWarning() << "Reply error:" << error;
|
qWarning() << "Reply error:" << error;
|
||||||
m_requestsDone ++;
|
processSTSError(error, replyData, headers);
|
||||||
|
failResult(m_mcAuthSucceeded);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token temp;
|
Katabasis::Token temp;
|
||||||
if(!parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
|
if(!parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
|
||||||
qWarning() << "Could not parse authorization response for access to mojang services...";
|
qWarning() << "Could not parse authorization response for access to mojang services...";
|
||||||
m_requestsDone ++;
|
failResult(m_mcAuthSucceeded);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
|
if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
|
||||||
qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
|
qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
|
||||||
qDebug() << replyData;
|
failResult(m_mcAuthSucceeded);
|
||||||
m_requestsDone ++;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_data->mojangservicesToken = temp;
|
m_data->mojangservicesToken = temp;
|
||||||
@ -385,61 +415,6 @@ void AuthContext::onSTSAuthMinecraftDone(
|
|||||||
doMinecraftAuth();
|
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,
|
|
||||||
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, "STSAuthGaneric")) {
|
|
||||||
qWarning() << "Could not parse authorization response for access to xbox API...";
|
|
||||||
m_requestsDone ++;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
m_data->xboxApiToken = temp;
|
|
||||||
|
|
||||||
doXBoxProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AuthContext::doMinecraftAuth() {
|
void AuthContext::doMinecraftAuth() {
|
||||||
QString mc_auth_template = R"XXX(
|
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"));
|
QNetworkRequest request = QNetworkRequest(QUrl("https://api.minecraftservices.com/authentication/login_with_xbox"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Accept", "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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onMinecraftAuthDone);
|
||||||
requestor->post(request, data.toUtf8());
|
requestor->post(request, data.toUtf8());
|
||||||
qDebug() << "Getting Minecraft access token...";
|
qDebug() << "Getting Minecraft access token...";
|
||||||
@ -498,18 +473,16 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AuthContext::onMinecraftAuthDone(
|
void AuthContext::onMinecraftAuthDone(
|
||||||
int requestId,
|
|
||||||
QNetworkReply::NetworkError error,
|
QNetworkReply::NetworkError error,
|
||||||
QByteArray replyData,
|
QByteArray replyData,
|
||||||
QList<QNetworkReply::RawHeaderPair> headers
|
QList<QNetworkReply::RawHeaderPair> headers
|
||||||
) {
|
) {
|
||||||
m_requestsDone ++;
|
|
||||||
|
|
||||||
if (error != QNetworkReply::NoError) {
|
if (error != QNetworkReply::NoError) {
|
||||||
qWarning() << "Reply error:" << error;
|
qWarning() << "Reply error:" << error;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
qDebug() << replyData;
|
qDebug() << replyData;
|
||||||
#endif
|
#endif
|
||||||
|
failResult(m_mcAuthSucceeded);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,11 +491,67 @@ void AuthContext::onMinecraftAuthDone(
|
|||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
qDebug() << replyData;
|
qDebug() << replyData;
|
||||||
#endif
|
#endif
|
||||||
|
failResult(m_mcAuthSucceeded);
|
||||||
return;
|
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() {
|
void AuthContext::doXBoxProfile() {
|
||||||
@ -543,25 +572,23 @@ void AuthContext::doXBoxProfile() {
|
|||||||
request.setRawHeader("Accept", "application/json");
|
request.setRawHeader("Accept", "application/json");
|
||||||
request.setRawHeader("x-xbl-contract-version", "3");
|
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());
|
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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onXBoxProfileDone);
|
||||||
requestor->get(request);
|
requestor->get(request);
|
||||||
qDebug() << "Getting Xbox profile...";
|
qDebug() << "Getting Xbox profile...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthContext::onXBoxProfileDone(
|
void AuthContext::onXBoxProfileDone(
|
||||||
int requestId,
|
|
||||||
QNetworkReply::NetworkError error,
|
QNetworkReply::NetworkError error,
|
||||||
QByteArray replyData,
|
QByteArray replyData,
|
||||||
QList<QNetworkReply::RawHeaderPair> headers
|
QList<QNetworkReply::RawHeaderPair> headers
|
||||||
) {
|
) {
|
||||||
m_requestsDone ++;
|
|
||||||
|
|
||||||
if (error != QNetworkReply::NoError) {
|
if (error != QNetworkReply::NoError) {
|
||||||
qWarning() << "Reply error:" << error;
|
qWarning() << "Reply error:" << error;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
qDebug() << replyData;
|
qDebug() << replyData;
|
||||||
#endif
|
#endif
|
||||||
|
failResult(m_xboxProfileSucceeded);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,7 +596,18 @@ void AuthContext::onXBoxProfileDone(
|
|||||||
qDebug() << "XBox profile: " << replyData;
|
qDebug() << "XBox profile: " << replyData;
|
||||||
#endif
|
#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();
|
checkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,7 +622,42 @@ void AuthContext::checkResult() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
finishActivity();
|
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("Accept", "application/json");
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onMinecraftProfileDone);
|
||||||
requestor->get(request);
|
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;
|
qDebug() << data;
|
||||||
|
#endif
|
||||||
if (error == QNetworkReply::ContentNotFoundError) {
|
if (error == QNetworkReply::ContentNotFoundError) {
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
m_data->minecraftProfile = MinecraftProfile();
|
||||||
finishActivity();
|
finishActivity();
|
||||||
@ -720,7 +799,7 @@ void AuthContext::doMigrationEligibilityCheck() {
|
|||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onMigrationEligibilityCheckDone);
|
||||||
requestor->get(request);
|
requestor->get(request);
|
||||||
}
|
}
|
||||||
@ -755,7 +834,11 @@ bool parseRolloutResponse(QByteArray & data, bool& result) {
|
|||||||
return true;
|
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) {
|
if (error == QNetworkReply::NoError) {
|
||||||
parseRolloutResponse(data, m_data->canMigrateToMSA);
|
parseRolloutResponse(data, m_data->canMigrateToMSA);
|
||||||
}
|
}
|
||||||
@ -768,12 +851,16 @@ void AuthContext::doGetSkin() {
|
|||||||
|
|
||||||
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
||||||
QNetworkRequest request = QNetworkRequest(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);
|
connect(requestor, &Requestor::finished, this, &AuthContext::onSkinDone);
|
||||||
requestor->get(request);
|
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) {
|
if (error == QNetworkReply::NoError) {
|
||||||
m_data->minecraftProfile.skin.data = data;
|
m_data->minecraftProfile.skin.data = data;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <QSet>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
@ -48,27 +49,31 @@ protected:
|
|||||||
void initMojang();
|
void initMojang();
|
||||||
|
|
||||||
void doUserAuth();
|
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();
|
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();
|
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();
|
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();
|
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();
|
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();
|
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();
|
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();
|
void checkResult();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -83,6 +88,10 @@ protected:
|
|||||||
int m_requestsDone = 0;
|
int m_requestsDone = 0;
|
||||||
bool m_xboxProfileSucceeded = false;
|
bool m_xboxProfileSucceeded = false;
|
||||||
bool m_mcAuthSucceeded = false;
|
bool m_mcAuthSucceeded = false;
|
||||||
|
|
||||||
|
QSet<int64_t> stsErrors;
|
||||||
|
bool stsFailed = false;
|
||||||
|
|
||||||
Katabasis::Activity m_activity = Katabasis::Activity::Idle;
|
Katabasis::Activity m_activity = Katabasis::Activity::Idle;
|
||||||
enum class AuthStage {
|
enum class AuthStage {
|
||||||
Initial,
|
Initial,
|
||||||
@ -95,6 +104,4 @@ protected:
|
|||||||
} m_stage = AuthStage::Initial;
|
} m_stage = AuthStage::Initial;
|
||||||
|
|
||||||
void setStage(AuthStage stage);
|
void setStage(AuthStage stage);
|
||||||
|
|
||||||
QNetworkAccessManager *mgr = nullptr;
|
|
||||||
};
|
};
|
||||||
|
121
launcher/minecraft/auth/flows/AuthRequest.cpp
Normal file
121
launcher/minecraft/auth/flows/AuthRequest.cpp
Normal file
@ -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);
|
||||||
|
}
|
65
launcher/minecraft/auth/flows/AuthRequest.h
Normal file
65
launcher/minecraft/auth/flows/AuthRequest.h
Normal file
@ -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_;
|
||||||
|
};
|
@ -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_;
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user