2021-07-22 23:45:20 +05:30
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QDesktopServices>
|
|
|
|
#include <QMetaEnum>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QJsonArray>
|
|
|
|
|
2021-11-10 07:46:04 +05:30
|
|
|
#include <QUuid>
|
|
|
|
|
2021-07-22 23:45:20 +05:30
|
|
|
#include <QUrlQuery>
|
|
|
|
|
|
|
|
#include <QPixmap>
|
|
|
|
#include <QPainter>
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
#include "AuthContext.h"
|
2021-07-22 23:45:20 +05:30
|
|
|
#include "katabasis/Globals.h"
|
2021-08-31 04:25:56 +05:30
|
|
|
#include "AuthRequest.h"
|
2021-08-28 02:05:17 +05:30
|
|
|
|
|
|
|
#include "Secrets.h"
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
#include "Env.h"
|
|
|
|
|
2021-11-10 07:32:51 +05:30
|
|
|
#include "Parsers.h"
|
|
|
|
|
2021-07-22 23:45:20 +05:30
|
|
|
using OAuth2 = Katabasis::OAuth2;
|
|
|
|
using Activity = Katabasis::Activity;
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
AuthContext::AuthContext(AccountData * data, QObject *parent) :
|
|
|
|
AccountTask(data, parent)
|
2021-07-22 23:45:20 +05:30
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::beginActivity(Activity activity) {
|
2021-07-22 23:45:20 +05:30
|
|
|
if(isBusy()) {
|
|
|
|
throw 0;
|
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
m_activity = activity;
|
|
|
|
changeState(STATE_WORKING, "Initializing");
|
|
|
|
emit activityChanged(m_activity);
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::finishActivity() {
|
2021-07-22 23:45:20 +05:30
|
|
|
if(!isBusy()) {
|
|
|
|
throw 0;
|
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
m_activity = Katabasis::Activity::Idle;
|
2021-08-22 23:31:18 +05:30
|
|
|
setStage(AuthStage::Complete);
|
2021-07-27 01:14:11 +05:30
|
|
|
m_data->validity_ = m_data->minecraftProfile.validity;
|
|
|
|
emit activityChanged(m_activity);
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::initMSA() {
|
|
|
|
if(m_oauth2) {
|
|
|
|
return;
|
|
|
|
}
|
2021-09-05 21:53:49 +05:30
|
|
|
|
|
|
|
auto clientId = Secrets::getMSAClientID('-');
|
|
|
|
if(clientId.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
Katabasis::OAuth2::Options opts;
|
|
|
|
opts.scope = "XboxLive.signin offline_access";
|
2021-09-05 21:53:49 +05:30
|
|
|
opts.clientIdentifier = clientId;
|
2021-08-22 23:31:18 +05:30
|
|
|
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
|
|
|
|
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
|
2021-07-27 01:14:11 +05:30
|
|
|
opts.listenerPorts = {28562, 28563, 28564, 28565, 28566};
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-11-17 17:50:10 +05:30
|
|
|
m_oauth2 = new OAuth2(opts, m_data->msaToken, this, &ENV.network());
|
2021-08-22 23:31:18 +05:30
|
|
|
m_oauth2->setGrantFlow(Katabasis::OAuth2::GrantFlowDevice);
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
connect(m_oauth2, &OAuth2::linkingFailed, this, &AuthContext::onOAuthLinkingFailed);
|
|
|
|
connect(m_oauth2, &OAuth2::linkingSucceeded, this, &AuthContext::onOAuthLinkingSucceeded);
|
2021-08-22 23:31:18 +05:30
|
|
|
connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &AuthContext::showVerificationUriAndCode);
|
2021-07-27 01:14:11 +05:30
|
|
|
connect(m_oauth2, &OAuth2::activityChanged, this, &AuthContext::onOAuthActivityChanged);
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::initMojang() {
|
|
|
|
if(m_yggdrasil) {
|
|
|
|
return;
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
m_yggdrasil = new Yggdrasil(m_data, this);
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
connect(m_yggdrasil, &Task::failed, this, &AuthContext::onMojangFailed);
|
|
|
|
connect(m_yggdrasil, &Task::succeeded, this, &AuthContext::onMojangSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onMojangSucceeded() {
|
2021-11-10 07:32:51 +05:30
|
|
|
doEntitlements();
|
2021-07-27 01:14:11 +05:30
|
|
|
}
|
2021-07-22 23:45:20 +05:30
|
|
|
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onMojangFailed() {
|
|
|
|
finishActivity();
|
|
|
|
m_error = m_yggdrasil->m_error;
|
|
|
|
m_aborted = m_yggdrasil->m_aborted;
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(m_yggdrasil->accountState(), tr("Mojang user authentication failed."));
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
/*
|
|
|
|
bool AuthContext::signOut() {
|
2021-07-22 23:45:20 +05:30
|
|
|
if(isBusy()) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
|
|
|
|
start();
|
|
|
|
|
2021-07-22 23:45:20 +05:30
|
|
|
beginActivity(Activity::LoggingOut);
|
2021-07-27 01:14:11 +05:30
|
|
|
m_oauth2->unlink();
|
2021-07-22 23:45:20 +05:30
|
|
|
m_account = AccountData();
|
|
|
|
finishActivity();
|
|
|
|
return true;
|
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
*/
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onOAuthLinkingFailed() {
|
2021-08-22 23:31:18 +05:30
|
|
|
emit hideVerificationUriAndCode();
|
2021-07-22 23:45:20 +05:30
|
|
|
finishActivity();
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onOAuthLinkingSucceeded() {
|
2021-08-22 23:31:18 +05:30
|
|
|
emit hideVerificationUriAndCode();
|
2021-07-22 23:45:20 +05:30
|
|
|
auto *o2t = qobject_cast<OAuth2 *>(sender());
|
|
|
|
if (!o2t->linked()) {
|
|
|
|
finishActivity();
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_FAILED_HARD, tr("Microsoft user authentication ended with an impossible state (succeeded, but not succeeded at the same time)."));
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
QVariantMap extraTokens = o2t->extraTokens();
|
2021-08-19 04:13:19 +05:30
|
|
|
#ifndef NDEBUG
|
2021-07-22 23:45:20 +05:30
|
|
|
if (!extraTokens.isEmpty()) {
|
|
|
|
qDebug() << "Extra tokens in response:";
|
|
|
|
foreach (QString key, extraTokens.keys()) {
|
|
|
|
qDebug() << "\t" << key << ":" << extraTokens.value(key);
|
|
|
|
}
|
|
|
|
}
|
2021-08-19 04:13:19 +05:30
|
|
|
#endif
|
2021-07-22 23:45:20 +05:30
|
|
|
doUserAuth();
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onOAuthActivityChanged(Katabasis::Activity activity) {
|
2021-07-22 23:45:20 +05:30
|
|
|
// respond to activity change here
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::doUserAuth() {
|
2021-08-22 23:31:18 +05:30
|
|
|
setStage(AuthStage::UserAuth);
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_WORKING, tr("Starting user authentication"));
|
2021-07-27 01:14:11 +05:30
|
|
|
|
2021-07-22 23:45:20 +05:30
|
|
|
QString xbox_auth_template = R"XXX(
|
|
|
|
{
|
|
|
|
"Properties": {
|
|
|
|
"AuthMethod": "RPS",
|
|
|
|
"SiteName": "user.auth.xboxlive.com",
|
|
|
|
"RpsTicket": "d=%1"
|
|
|
|
},
|
|
|
|
"RelyingParty": "http://auth.xboxlive.com",
|
|
|
|
"TokenType": "JWT"
|
|
|
|
}
|
|
|
|
)XXX";
|
2021-07-27 01:14:11 +05:30
|
|
|
auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
|
2021-07-22 23:45:20 +05:30
|
|
|
|
|
|
|
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
request.setRawHeader("Accept", "application/json");
|
2021-09-22 01:32:12 +05:30
|
|
|
auto *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onUserAuthDone);
|
2021-07-22 23:45:20 +05:30
|
|
|
requestor->post(request, xbox_auth_data.toUtf8());
|
|
|
|
qDebug() << "First layer of XBox auth ... commencing.";
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onUserAuthDone(
|
2021-07-22 23:45:20 +05:30
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray replyData,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
|
|
|
if (error != QNetworkReply::NoError) {
|
|
|
|
qWarning() << "Reply error:" << error;
|
|
|
|
finishActivity();
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_FAILED_HARD, tr("XBox user authentication failed."));
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Katabasis::Token temp;
|
2021-11-10 07:32:51 +05:30
|
|
|
if(!Parsers::parseXTokenResponse(replyData, temp, "UToken")) {
|
2021-07-22 23:45:20 +05:30
|
|
|
qWarning() << "Could not parse user authentication response...";
|
|
|
|
finishActivity();
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_FAILED_HARD, tr("XBox user authentication response could not be understood."));
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
m_data->userToken = temp;
|
|
|
|
|
2021-08-22 23:31:18 +05:30
|
|
|
setStage(AuthStage::XboxAuth);
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_WORKING, tr("Starting XBox authentication"));
|
2021-07-22 23:45:20 +05:30
|
|
|
|
|
|
|
doSTSAuthMinecraft();
|
|
|
|
doSTSAuthGeneric();
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
url = "https://xsts.auth.xboxlive.com/xsts/authorize"
|
|
|
|
headers = {"x-xbl-contract-version": "1"}
|
|
|
|
data = {
|
|
|
|
"RelyingParty": relying_party,
|
|
|
|
"TokenType": "JWT",
|
|
|
|
"Properties": {
|
|
|
|
"UserTokens": [self.user_token.token],
|
|
|
|
"SandboxId": "RETAIL",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
*/
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::doSTSAuthMinecraft() {
|
2021-07-22 23:45:20 +05:30
|
|
|
QString xbox_auth_template = R"XXX(
|
|
|
|
{
|
|
|
|
"Properties": {
|
|
|
|
"SandboxId": "RETAIL",
|
|
|
|
"UserTokens": [
|
|
|
|
"%1"
|
|
|
|
]
|
|
|
|
},
|
|
|
|
"RelyingParty": "rp://api.minecraftservices.com/",
|
|
|
|
"TokenType": "JWT"
|
|
|
|
}
|
|
|
|
)XXX";
|
2021-07-27 01:14:11 +05:30
|
|
|
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token);
|
2021-07-22 23:45:20 +05:30
|
|
|
|
|
|
|
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
request.setRawHeader("Accept", "application/json");
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onSTSAuthMinecraftDone);
|
2021-07-22 23:45:20 +05:30
|
|
|
requestor->post(request, xbox_auth_data.toUtf8());
|
2021-08-19 04:13:19 +05:30
|
|
|
qDebug() << "Getting Minecraft services STS token...";
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
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;
|
|
|
|
}
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
int64_t errorCode = -1;
|
|
|
|
auto obj = doc.object();
|
2021-11-10 07:32:51 +05:30
|
|
|
if(!Parsers::getNumber(obj.value("XErr"), errorCode)) {
|
2021-08-31 04:25:56 +05:30
|
|
|
qWarning() << "XErr is not a number";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
stsErrors.insert(errorCode);
|
|
|
|
stsFailed = true;
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
void AuthContext::onSTSAuthMinecraftDone(
|
2021-07-22 23:45:20 +05:30
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray replyData,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
2021-08-31 04:25:56 +05:30
|
|
|
#ifndef NDEBUG
|
|
|
|
qDebug() << replyData;
|
|
|
|
#endif
|
2021-07-22 23:45:20 +05:30
|
|
|
if (error != QNetworkReply::NoError) {
|
|
|
|
qWarning() << "Reply error:" << error;
|
2021-08-31 04:25:56 +05:30
|
|
|
processSTSError(error, replyData, headers);
|
|
|
|
failResult(m_mcAuthSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Katabasis::Token temp;
|
2021-11-10 07:32:51 +05:30
|
|
|
if(!Parsers::parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
|
2021-08-31 04:25:56 +05:30
|
|
|
qWarning() << "Could not parse authorization response for access to mojang services...";
|
|
|
|
failResult(m_mcAuthSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
|
2021-07-22 23:45:20 +05:30
|
|
|
qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
|
2021-08-31 04:25:56 +05:30
|
|
|
failResult(m_mcAuthSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
2021-08-31 04:25:56 +05:30
|
|
|
m_data->mojangservicesToken = temp;
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
doMinecraftAuth();
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::doMinecraftAuth() {
|
2021-11-10 07:32:51 +05:30
|
|
|
auto requestURL = "https://api.minecraftservices.com/launcher/login";
|
|
|
|
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
|
|
|
auto xToken = m_data->mojangservicesToken.token;
|
|
|
|
|
2021-07-22 23:45:20 +05:30
|
|
|
QString mc_auth_template = R"XXX(
|
|
|
|
{
|
2021-11-10 07:32:51 +05:30
|
|
|
"xtoken": "XBL3.0 x=%1;%2",
|
|
|
|
"platform": "PC_LAUNCHER"
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
)XXX";
|
2021-11-10 07:32:51 +05:30
|
|
|
auto requestBody = mc_auth_template.arg(uhs, xToken);
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-11-10 07:32:51 +05:30
|
|
|
QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
|
2021-07-22 23:45:20 +05:30
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
request.setRawHeader("Accept", "application/json");
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onMinecraftAuthDone);
|
2021-11-10 07:32:51 +05:30
|
|
|
requestor->post(request, requestBody.toUtf8());
|
2021-07-22 23:45:20 +05:30
|
|
|
qDebug() << "Getting Minecraft access token...";
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onMinecraftAuthDone(
|
2021-07-22 23:45:20 +05:30
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray replyData,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
2021-11-10 07:32:51 +05:30
|
|
|
qDebug() << replyData;
|
2021-07-22 23:45:20 +05:30
|
|
|
if (error != QNetworkReply::NoError) {
|
|
|
|
qWarning() << "Reply error:" << error;
|
2021-08-19 04:13:19 +05:30
|
|
|
#ifndef NDEBUG
|
2021-07-22 23:45:20 +05:30
|
|
|
qDebug() << replyData;
|
2021-08-19 04:13:19 +05:30
|
|
|
#endif
|
2021-08-31 04:25:56 +05:30
|
|
|
failResult(m_mcAuthSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-10 07:32:51 +05:30
|
|
|
if(!Parsers::parseMojangResponse(replyData, m_data->yggdrasilToken)) {
|
2021-07-22 23:45:20 +05:30
|
|
|
qWarning() << "Could not parse login_with_xbox response...";
|
2021-08-19 04:13:19 +05:30
|
|
|
#ifndef NDEBUG
|
2021-07-22 23:45:20 +05:30
|
|
|
qDebug() << replyData;
|
2021-08-19 04:13:19 +05:30
|
|
|
#endif
|
2021-08-31 04:25:56 +05:30
|
|
|
failResult(m_mcAuthSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
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");
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onSTSAuthGenericDone);
|
2021-08-31 04:25:56 +05:30
|
|
|
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;
|
2021-11-10 07:32:51 +05:30
|
|
|
if(!Parsers::parseXTokenResponse(replyData, temp, "STSAuthGeneric")) {
|
2021-08-31 04:25:56 +05:30
|
|
|
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();
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::doXBoxProfile() {
|
2021-07-22 23:45:20 +05:30
|
|
|
auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings");
|
|
|
|
QUrlQuery q;
|
|
|
|
q.addQueryItem(
|
|
|
|
"settings",
|
|
|
|
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
|
|
|
"PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,"
|
|
|
|
"UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,"
|
|
|
|
"PreferredColor,Location,Bio,Watermarks,"
|
|
|
|
"RealName,RealNameOverride,IsQuarantined"
|
|
|
|
);
|
|
|
|
url.setQuery(q);
|
|
|
|
|
|
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
request.setRawHeader("Accept", "application/json");
|
|
|
|
request.setRawHeader("x-xbl-contract-version", "3");
|
2021-07-27 01:14:11 +05:30
|
|
|
request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onXBoxProfileDone);
|
2021-07-22 23:45:20 +05:30
|
|
|
requestor->get(request);
|
|
|
|
qDebug() << "Getting Xbox profile...";
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::onXBoxProfileDone(
|
2021-07-22 23:45:20 +05:30
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray replyData,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
|
|
|
if (error != QNetworkReply::NoError) {
|
|
|
|
qWarning() << "Reply error:" << error;
|
2021-08-19 04:13:19 +05:30
|
|
|
#ifndef NDEBUG
|
2021-07-22 23:45:20 +05:30
|
|
|
qDebug() << replyData;
|
2021-08-19 04:13:19 +05:30
|
|
|
#endif
|
2021-08-31 04:25:56 +05:30
|
|
|
failResult(m_xboxProfileSucceeded);
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-19 04:13:19 +05:30
|
|
|
#ifndef NDEBUG
|
2021-07-22 23:45:20 +05:30
|
|
|
qDebug() << "XBox profile: " << replyData;
|
2021-08-19 04:13:19 +05:30
|
|
|
#endif
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
succeedResult(m_xboxProfileSucceeded);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AuthContext::succeedResult(bool& flag) {
|
|
|
|
m_requestsDone ++;
|
|
|
|
flag = true;
|
|
|
|
checkResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AuthContext::failResult(bool& flag) {
|
|
|
|
m_requestsDone ++;
|
|
|
|
flag = false;
|
2021-07-22 23:45:20 +05:30
|
|
|
checkResult();
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::checkResult() {
|
2021-08-20 05:04:32 +05:30
|
|
|
qDebug() << "AuthContext::checkResult called";
|
2021-07-27 01:14:11 +05:30
|
|
|
if(m_requestsDone != 2) {
|
2021-08-20 05:04:32 +05:30
|
|
|
qDebug() << "Number of ready results:" << m_requestsDone;
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
2021-07-27 01:14:11 +05:30
|
|
|
if(m_mcAuthSucceeded && m_xboxProfileSucceeded) {
|
2021-11-10 07:32:51 +05:30
|
|
|
doEntitlements();
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
else {
|
|
|
|
finishActivity();
|
2021-08-31 04:25:56 +05:30
|
|
|
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.")
|
2021-09-15 16:16:07 +05:30
|
|
|
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>")
|
2021-08-31 04:25:56 +05:30
|
|
|
);
|
|
|
|
}
|
|
|
|
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"));
|
|
|
|
}
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-10 07:32:51 +05:30
|
|
|
void AuthContext::doEntitlements() {
|
|
|
|
auto uuid = QUuid::createUuid();
|
|
|
|
entitlementsRequestId = uuid.toString().remove('{').remove('}');
|
|
|
|
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + entitlementsRequestId;
|
|
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
request.setRawHeader("Accept", "application/json");
|
|
|
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onEntitlementsDone);
|
|
|
|
requestor->get(request);
|
|
|
|
qDebug() << "Getting Xbox profile...";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void AuthContext::onEntitlementsDone(
|
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray data,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
2021-08-19 04:13:19 +05:30
|
|
|
#ifndef NDEBUG
|
|
|
|
qDebug() << data;
|
|
|
|
#endif
|
2021-11-10 07:32:51 +05:30
|
|
|
// TODO: check presence of same entitlementsRequestId?
|
|
|
|
// TODO: validate JWTs?
|
|
|
|
Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement);
|
|
|
|
doMinecraftProfile();
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::doMinecraftProfile() {
|
2021-08-22 23:31:18 +05:30
|
|
|
setStage(AuthStage::MinecraftProfile);
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_WORKING, tr("Starting minecraft profile acquisition"));
|
2021-07-27 01:14:11 +05:30
|
|
|
|
2021-07-22 23:45:20 +05:30
|
|
|
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
|
|
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
// request.setRawHeader("Accept", "application/json");
|
2021-07-27 01:14:11 +05:30
|
|
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
2021-07-22 23:45:20 +05:30
|
|
|
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onMinecraftProfileDone);
|
2021-07-22 23:45:20 +05:30
|
|
|
requestor->get(request);
|
|
|
|
}
|
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
void AuthContext::onMinecraftProfileDone(
|
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray data,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
|
|
|
#ifndef NDEBUG
|
2021-07-22 23:45:20 +05:30
|
|
|
qDebug() << data;
|
2021-08-31 04:25:56 +05:30
|
|
|
#endif
|
2021-07-22 23:45:20 +05:30
|
|
|
if (error == QNetworkReply::ContentNotFoundError) {
|
2021-11-10 07:32:51 +05:30
|
|
|
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
2021-07-27 01:14:11 +05:30
|
|
|
m_data->minecraftProfile = MinecraftProfile();
|
2021-11-10 07:32:51 +05:30
|
|
|
succeed();
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (error != QNetworkReply::NoError) {
|
|
|
|
finishActivity();
|
2021-08-20 05:04:32 +05:30
|
|
|
changeState(STATE_FAILED_HARD, tr("Minecraft Java profile acquisition failed."));
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
2021-11-10 07:32:51 +05:30
|
|
|
if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
2021-07-27 01:14:11 +05:30
|
|
|
m_data->minecraftProfile = MinecraftProfile();
|
2021-07-22 23:45:20 +05:30
|
|
|
finishActivity();
|
2021-08-20 05:04:32 +05:30
|
|
|
changeState(STATE_FAILED_HARD, tr("Minecraft Java profile response could not be parsed"));
|
2021-07-22 23:45:20 +05:30
|
|
|
return;
|
|
|
|
}
|
2021-08-30 02:25:33 +05:30
|
|
|
|
|
|
|
if(m_data->type == AccountType::Mojang) {
|
|
|
|
doMigrationEligibilityCheck();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
doGetSkin();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AuthContext::doMigrationEligibilityCheck() {
|
|
|
|
setStage(AuthStage::MigrationEligibility);
|
|
|
|
changeState(STATE_WORKING, tr("Starting check for migration eligibility"));
|
|
|
|
|
|
|
|
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
|
|
|
|
QNetworkRequest request = QNetworkRequest(url);
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
|
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
|
|
|
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onMigrationEligibilityCheckDone);
|
2021-08-30 02:25:33 +05:30
|
|
|
requestor->get(request);
|
|
|
|
}
|
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
void AuthContext::onMigrationEligibilityCheckDone(
|
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray data,
|
|
|
|
QList<QNetworkReply::RawHeaderPair> headers
|
|
|
|
) {
|
2021-08-30 02:25:33 +05:30
|
|
|
if (error == QNetworkReply::NoError) {
|
2021-11-10 07:32:51 +05:30
|
|
|
Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
|
2021-08-30 02:25:33 +05:30
|
|
|
}
|
2021-07-22 23:45:20 +05:30
|
|
|
doGetSkin();
|
|
|
|
}
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
void AuthContext::doGetSkin() {
|
2021-08-22 23:31:18 +05:30
|
|
|
setStage(AuthStage::Skin);
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_WORKING, tr("Fetching player skin"));
|
2021-07-27 01:14:11 +05:30
|
|
|
|
|
|
|
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
2021-07-22 23:45:20 +05:30
|
|
|
QNetworkRequest request = QNetworkRequest(url);
|
2021-09-22 01:32:12 +05:30
|
|
|
AuthRequest *requestor = new AuthRequest(this);
|
|
|
|
connect(requestor, &AuthRequest::finished, this, &AuthContext::onSkinDone);
|
2021-07-22 23:45:20 +05:30
|
|
|
requestor->get(request);
|
|
|
|
}
|
|
|
|
|
2021-08-31 04:25:56 +05:30
|
|
|
void AuthContext::onSkinDone(
|
|
|
|
QNetworkReply::NetworkError error,
|
|
|
|
QByteArray data,
|
|
|
|
QList<QNetworkReply::RawHeaderPair>
|
|
|
|
) {
|
2021-07-22 23:45:20 +05:30
|
|
|
if (error == QNetworkReply::NoError) {
|
2021-07-27 01:14:11 +05:30
|
|
|
m_data->minecraftProfile.skin.data = data;
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
2021-11-10 07:32:51 +05:30
|
|
|
succeed();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void AuthContext::succeed() {
|
2021-07-27 01:14:11 +05:30
|
|
|
m_data->validity_ = Katabasis::Validity::Certain;
|
2021-07-22 23:45:20 +05:30
|
|
|
finishActivity();
|
2021-08-16 03:10:37 +05:30
|
|
|
changeState(STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
|
2021-08-22 23:31:18 +05:30
|
|
|
void AuthContext::setStage(AuthContext::AuthStage stage) {
|
|
|
|
m_stage = stage;
|
|
|
|
emit progress((int)m_stage, (int)AuthStage::Complete);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-27 01:14:11 +05:30
|
|
|
QString AuthContext::getStateMessage() const {
|
|
|
|
switch (m_accountState)
|
2021-07-22 23:45:20 +05:30
|
|
|
{
|
2021-07-27 01:14:11 +05:30
|
|
|
case STATE_WORKING:
|
|
|
|
switch(m_stage) {
|
2021-08-22 23:31:18 +05:30
|
|
|
case AuthStage::Initial: {
|
2021-07-27 01:14:11 +05:30
|
|
|
QString loginMessage = tr("Logging in as %1 user");
|
|
|
|
if(m_data->type == AccountType::MSA) {
|
|
|
|
return loginMessage.arg("Microsoft");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return loginMessage.arg("Mojang");
|
|
|
|
}
|
|
|
|
}
|
2021-08-22 23:31:18 +05:30
|
|
|
case AuthStage::UserAuth:
|
2021-07-27 01:14:11 +05:30
|
|
|
return tr("Logging in as XBox user");
|
2021-08-22 23:31:18 +05:30
|
|
|
case AuthStage::XboxAuth:
|
2021-07-27 01:14:11 +05:30
|
|
|
return tr("Logging in with XBox and Mojang services");
|
2021-08-22 23:31:18 +05:30
|
|
|
case AuthStage::MinecraftProfile:
|
2021-07-27 01:14:11 +05:30
|
|
|
return tr("Getting Minecraft profile");
|
2021-08-30 02:25:33 +05:30
|
|
|
case AuthStage::MigrationEligibility:
|
|
|
|
return tr("Checking for migration eligibility");
|
2021-08-22 23:31:18 +05:30
|
|
|
case AuthStage::Skin:
|
2021-07-27 01:14:11 +05:30
|
|
|
return tr("Getting Minecraft skin");
|
2021-08-22 23:31:18 +05:30
|
|
|
case AuthStage::Complete:
|
|
|
|
return tr("Finished");
|
2021-07-27 01:14:11 +05:30
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return AccountTask::getStateMessage();
|
2021-07-22 23:45:20 +05:30
|
|
|
}
|
|
|
|
}
|