aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/auth/flows
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/auth/flows')
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.cpp466
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.h13
-rw-r--r--launcher/minecraft/auth/flows/AuthRequest.cpp6
-rw-r--r--launcher/minecraft/auth/flows/AuthRequest.h1
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.cpp8
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.h5
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.h5
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.cpp6
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.h6
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.cpp5
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.h2
-rw-r--r--launcher/minecraft/auth/flows/Parsers.cpp316
-rw-r--r--launcher/minecraft/auth/flows/Parsers.h19
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.cpp72
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.h6
15 files changed, 519 insertions, 417 deletions
diff --git a/launcher/minecraft/auth/flows/AuthContext.cpp b/launcher/minecraft/auth/flows/AuthContext.cpp
index 9fb3ec48..00957fd4 100644
--- a/launcher/minecraft/auth/flows/AuthContext.cpp
+++ b/launcher/minecraft/auth/flows/AuthContext.cpp
@@ -4,25 +4,21 @@
#include <QDesktopServices>
#include <QMetaEnum>
#include <QDebug>
-
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
-
+#include <QUuid>
#include <QUrlQuery>
-#include <QPixmap>
-#include <QPainter>
-
#include "AuthContext.h"
#include "katabasis/Globals.h"
#include "AuthRequest.h"
-#include "Secrets.h"
+#include "Parsers.h"
-#include "Env.h"
+#include <Application.h>
-using OAuth2 = Katabasis::OAuth2;
+using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity;
AuthContext::AuthContext(AccountData * data, QObject *parent) :
@@ -54,25 +50,17 @@ void AuthContext::initMSA() {
return;
}
- auto clientId = Secrets::getMSAClientID('-');
- if(clientId.isEmpty()) {
- return;
- }
-
- Katabasis::OAuth2::Options opts;
+ OAuth2::Options opts;
opts.scope = "XboxLive.signin offline_access";
- opts.clientIdentifier = clientId;
+ opts.clientIdentifier = APPLICATION->msaClientId();
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
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, &ENV.qnam());
- m_oauth2->setGrantFlow(Katabasis::OAuth2::GrantFlowDevice);
+ // FIXME: OAuth2 is not aware of our fancy shared pointers
+ m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get());
- connect(m_oauth2, &OAuth2::linkingFailed, this, &AuthContext::onOAuthLinkingFailed);
- connect(m_oauth2, &OAuth2::linkingSucceeded, this, &AuthContext::onOAuthLinkingSucceeded);
- connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &AuthContext::showVerificationUriAndCode);
connect(m_oauth2, &OAuth2::activityChanged, this, &AuthContext::onOAuthActivityChanged);
+ connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &AuthContext::showVerificationUriAndCode);
}
void AuthContext::initMojang() {
@@ -97,50 +85,56 @@ void AuthContext::onMojangFailed() {
changeState(m_yggdrasil->accountState(), tr("Mojang user authentication failed."));
}
-/*
-bool AuthContext::signOut() {
- if(isBusy()) {
- return false;
- }
-
- start();
-
- beginActivity(Activity::LoggingOut);
- m_oauth2->unlink();
- m_account = AccountData();
- finishActivity();
- return true;
-}
-*/
-
-void AuthContext::onOAuthLinkingFailed() {
- emit hideVerificationUriAndCode();
- finishActivity();
- changeState(STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
-}
-
-void AuthContext::onOAuthLinkingSucceeded() {
- emit hideVerificationUriAndCode();
- auto *o2t = qobject_cast<OAuth2 *>(sender());
- if (!o2t->linked()) {
- finishActivity();
- changeState(STATE_FAILED_HARD, tr("Microsoft user authentication ended with an impossible state (succeeded, but not succeeded at the same time)."));
- return;
- }
- QVariantMap extraTokens = o2t->extraTokens();
-#ifndef NDEBUG
- if (!extraTokens.isEmpty()) {
- qDebug() << "Extra tokens in response:";
- foreach (QString key, extraTokens.keys()) {
- qDebug() << "\t" << key << ":" << extraTokens.value(key);
+void AuthContext::onOAuthActivityChanged(Katabasis::Activity activity) {
+ switch(activity) {
+ case Katabasis::Activity::Idle:
+ case Katabasis::Activity::LoggingIn:
+ case Katabasis::Activity::Refreshing:
+ case Katabasis::Activity::LoggingOut: {
+ // We asked it to do something, it's doing it. Nothing to act upon.
+ return;
}
- }
+ case Katabasis::Activity::Succeeded: {
+ // Succeeded or did not invalidate tokens
+ emit hideVerificationUriAndCode();
+ if (!m_oauth2->linked()) {
+ finishActivity();
+ changeState(STATE_FAILED_HARD, tr("Microsoft user authentication ended with an impossible state (succeeded, but not succeeded at the same time)."));
+ return;
+ }
+ QVariantMap extraTokens = m_oauth2->extraTokens();
+#ifndef NDEBUG
+ if (!extraTokens.isEmpty()) {
+ qDebug() << "Extra tokens in response:";
+ foreach (QString key, extraTokens.keys()) {
+ qDebug() << "\t" << key << ":" << extraTokens.value(key);
+ }
+ }
#endif
- doUserAuth();
-}
+ doUserAuth();
+ return;
+ }
+ case Katabasis::Activity::FailedSoft: {
+ emit hideVerificationUriAndCode();
+ finishActivity();
+ changeState(STATE_FAILED_SOFT, tr("Microsoft user authentication failed with a soft error."));
+ return;
+ }
+ case Katabasis::Activity::FailedGone:
+ case Katabasis::Activity::FailedHard: {
+ emit hideVerificationUriAndCode();
+ finishActivity();
+ changeState(STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
+ return;
+ }
+ default: {
+ emit hideVerificationUriAndCode();
+ finishActivity();
+ changeState(STATE_FAILED_HARD, tr("Microsoft user authentication completed with an unrecognized result."));
+ return;
+ }
-void AuthContext::onOAuthActivityChanged(Katabasis::Activity activity) {
- // respond to activity change here
+ }
}
void AuthContext::doUserAuth() {
@@ -169,137 +163,6 @@ void AuthContext::doUserAuth() {
qDebug() << "First layer of XBox auth ... commencing.";
}
-namespace {
-bool getDateTime(QJsonValue value, QDateTime & out) {
- if(!value.isString()) {
- return false;
- }
- out = QDateTime::fromString(value.toString(), Qt::ISODate);
- return out.isValid();
-}
-
-bool getString(QJsonValue value, QString & out) {
- if(!value.isString()) {
- return false;
- }
- out = value.toString();
- return true;
-}
-
-bool getNumber(QJsonValue value, double & out) {
- if(!value.isDouble()) {
- return false;
- }
- out = value.toDouble();
- 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()) {
- return false;
- }
- out = value.toBool();
- return true;
-}
-
-/*
-{
- "IssueInstant":"2020-12-07T19:52:08.4463796Z",
- "NotAfter":"2020-12-21T19:52:08.4463796Z",
- "Token":"token",
- "DisplayClaims":{
- "xui":[
- {
- "uhs":"userhash"
- }
- ]
- }
- }
-*/
-// TODO: handle error responses ...
-/*
-{
- "Identity":"0",
- "XErr":2148916238,
- "Message":"",
- "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily"
-}
-// 2148916233 = missing XBox account
-// 2148916238 = child account not linked to a family
-*/
-
-bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, const char * name) {
- qDebug() << "Parsing" << name <<":";
-#ifndef NDEBUG
- qDebug() << data;
-#endif
- QJsonParseError jsonError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
- return false;
- }
-
- auto obj = doc.object();
- if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
- qWarning() << "User IssueInstant is not a timestamp";
- return false;
- }
- if(!getDateTime(obj.value("NotAfter"), output.notAfter)) {
- qWarning() << "User NotAfter is not a timestamp";
- return false;
- }
- if(!getString(obj.value("Token"), output.token)) {
- qWarning() << "User Token is not a timestamp";
- return false;
- }
- auto arrayVal = obj.value("DisplayClaims").toObject().value("xui");
- if(!arrayVal.isArray()) {
- qWarning() << "Missing xui claims array";
- return false;
- }
- bool foundUHS = false;
- for(auto item: arrayVal.toArray()) {
- if(!item.isObject()) {
- continue;
- }
- auto obj = item.toObject();
- if(obj.contains("uhs")) {
- foundUHS = true;
- } else {
- continue;
- }
- // consume all 'display claims' ... whatever that means
- for(auto iter = obj.begin(); iter != obj.end(); iter++) {
- QString claim;
- if(!getString(obj.value(iter.key()), claim)) {
- qWarning() << "display claim " << iter.key() << " is not a string...";
- return false;
- }
- output.extra[iter.key()] = claim;
- }
-
- break;
- }
- if(!foundUHS) {
- qWarning() << "Missing uhs";
- return false;
- }
- output.validity = Katabasis::Validity::Certain;
- qDebug() << name << "is valid.";
- return true;
-}
-
-}
-
void AuthContext::onUserAuthDone(
QNetworkReply::NetworkError error,
QByteArray replyData,
@@ -313,7 +176,7 @@ void AuthContext::onUserAuthDone(
}
Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp, "UToken")) {
+ if(!Parsers::parseXTokenResponse(replyData, temp, "UToken")) {
qWarning() << "Could not parse user authentication response...";
finishActivity();
changeState(STATE_FAILED_HARD, tr("XBox user authentication response could not be understood."));
@@ -365,7 +228,7 @@ void AuthContext::doSTSAuthMinecraft() {
void AuthContext::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) {
if(error == QNetworkReply::AuthenticationRequiredError) {
- QJsonParseError jsonError;
+ QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
@@ -374,7 +237,7 @@ void AuthContext::processSTSError(QNetworkReply::NetworkError error, QByteArray
int64_t errorCode = -1;
auto obj = doc.object();
- if(!getNumber(obj.value("XErr"), errorCode)) {
+ if(!Parsers::getNumber(obj.value("XErr"), errorCode)) {
qWarning() << "XErr is not a number";
return;
}
@@ -400,7 +263,7 @@ void AuthContext::onSTSAuthMinecraftDone(
}
Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
+ if(!Parsers::parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
qWarning() << "Could not parse authorization response for access to mojang services...";
failResult(m_mcAuthSucceeded);
return;
@@ -417,67 +280,33 @@ void AuthContext::onSTSAuthMinecraftDone(
}
void AuthContext::doMinecraftAuth() {
+ auto requestURL = "https://api.minecraftservices.com/launcher/login";
+ auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
+ auto xToken = m_data->mojangservicesToken.token;
+
QString mc_auth_template = R"XXX(
{
- "identityToken": "XBL3.0 x=%1;%2"
+ "xtoken": "XBL3.0 x=%1;%2",
+ "platform": "PC_LAUNCHER"
}
)XXX";
- auto data = mc_auth_template.arg(m_data->mojangservicesToken.extra["uhs"].toString(), m_data->mojangservicesToken.token);
+ auto requestBody = mc_auth_template.arg(uhs, xToken);
- QNetworkRequest request = QNetworkRequest(QUrl("https://api.minecraftservices.com/authentication/login_with_xbox"));
+ QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
AuthRequest *requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &AuthContext::onMinecraftAuthDone);
- requestor->post(request, data.toUtf8());
+ requestor->post(request, requestBody.toUtf8());
qDebug() << "Getting Minecraft access token...";
}
-namespace {
-bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
- QJsonParseError jsonError;
- qDebug() << "Parsing Mojang response...";
-#ifndef NDEBUG
- qDebug() << data;
-#endif
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from api.minecraftservices.com/authentication/login_with_xbox as JSON: " << jsonError.errorString();
- return false;
- }
-
- auto obj = doc.object();
- double expires_in = 0;
- if(!getNumber(obj.value("expires_in"), expires_in)) {
- qWarning() << "expires_in is not a valid number";
- return false;
- }
- auto currentTime = QDateTime::currentDateTimeUtc();
- output.issueInstant = currentTime;
- output.notAfter = currentTime.addSecs(expires_in);
-
- QString username;
- if(!getString(obj.value("username"), username)) {
- qWarning() << "username is not valid";
- return false;
- }
-
- // TODO: it's a JWT... validate it?
- if(!getString(obj.value("access_token"), output.token)) {
- qWarning() << "access_token is not valid";
- return false;
- }
- output.validity = Katabasis::Validity::Certain;
- qDebug() << "Mojang response is valid.";
- return true;
-}
-}
-
void AuthContext::onMinecraftAuthDone(
QNetworkReply::NetworkError error,
QByteArray replyData,
QList<QNetworkReply::RawHeaderPair> headers
) {
+ qDebug() << replyData;
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
#ifndef NDEBUG
@@ -487,7 +316,7 @@ void AuthContext::onMinecraftAuthDone(
return;
}
- if(!parseMojangResponse(replyData, m_data->yggdrasilToken)) {
+ if(!Parsers::parseMojangResponse(replyData, m_data->yggdrasilToken)) {
qWarning() << "Could not parse login_with_xbox response...";
#ifndef NDEBUG
qDebug() << replyData;
@@ -539,7 +368,7 @@ void AuthContext::onSTSAuthGenericDone(
}
Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp, "STSAuthGeneric")) {
+ if(!Parsers::parseXTokenResponse(replyData, temp, "STSAuthGeneric")) {
qWarning() << "Could not parse authorization response for access to xbox API...";
failResult(m_xboxProfileSucceeded);
return;
@@ -619,7 +448,7 @@ void AuthContext::checkResult() {
return;
}
if(m_mcAuthSucceeded && m_xboxProfileSucceeded) {
- doMinecraftProfile();
+ doEntitlements();
}
else {
finishActivity();
@@ -662,84 +491,33 @@ void AuthContext::checkResult() {
}
}
-namespace {
-bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
- qDebug() << "Parsing Minecraft profile...";
+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
+) {
#ifndef NDEBUG
qDebug() << data;
#endif
-
- QJsonParseError jsonError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
- return false;
- }
-
- auto obj = doc.object();
- if(!getString(obj.value("id"), output.id)) {
- qWarning() << "Minecraft profile id is not a string";
- return false;
- }
-
- if(!getString(obj.value("name"), output.name)) {
- qWarning() << "Minecraft profile name is not a string";
- return false;
- }
-
- auto skinsArray = obj.value("skins").toArray();
- for(auto skin: skinsArray) {
- auto skinObj = skin.toObject();
- Skin skinOut;
- if(!getString(skinObj.value("id"), skinOut.id)) {
- continue;
- }
- QString state;
- if(!getString(skinObj.value("state"), state)) {
- continue;
- }
- if(state != "ACTIVE") {
- continue;
- }
- if(!getString(skinObj.value("url"), skinOut.url)) {
- continue;
- }
- if(!getString(skinObj.value("variant"), skinOut.variant)) {
- continue;
- }
- // we deal with only the active skin
- output.skin = skinOut;
- break;
- }
- auto capesArray = obj.value("capes").toArray();
-
- QString currentCape;
- for(auto cape: capesArray) {
- auto capeObj = cape.toObject();
- Cape capeOut;
- if(!getString(capeObj.value("id"), capeOut.id)) {
- continue;
- }
- QString state;
- if(!getString(capeObj.value("state"), state)) {
- continue;
- }
- if(state == "ACTIVE") {
- currentCape = capeOut.id;
- }
- if(!getString(capeObj.value("url"), capeOut.url)) {
- continue;
- }
- if(!getString(capeObj.value("alias"), capeOut.alias)) {
- continue;
- }
-
- output.capes[capeOut.id] = capeOut;
- }
- output.currentCape = currentCape;
- output.validity = Katabasis::Validity::Certain;
- return true;
-}
+ // TODO: check presence of same entitlementsRequestId?
+ // TODO: validate JWTs?
+ Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement);
+ doMinecraftProfile();
}
void AuthContext::doMinecraftProfile() {
@@ -766,9 +544,13 @@ void AuthContext::onMinecraftProfileDone(
qDebug() << data;
#endif
if (error == QNetworkReply::ContentNotFoundError) {
+ // NOTE: Succeed even if we do not have a profile. This is a valid account state.
+ if(m_data->type == AccountType::Mojang) {
+ m_data->minecraftEntitlement.canPlayMinecraft = false;
+ m_data->minecraftEntitlement.ownsMinecraft = false;
+ }
m_data->minecraftProfile = MinecraftProfile();
- finishActivity();
- changeState(STATE_FAILED_HARD, tr("Account is missing a Minecraft Java profile.\n\nWhile the Microsoft account is valid, it does not own the game.\n\nYou might own Bedrock on this account, but that does not give you access to Java currently."));
+ succeed();
return;
}
if (error != QNetworkReply::NoError) {
@@ -776,7 +558,7 @@ void AuthContext::onMinecraftProfileDone(
changeState(STATE_FAILED_HARD, tr("Minecraft Java profile acquisition failed."));
return;
}
- if(!parseMinecraftProfile(data, m_data->minecraftProfile)) {
+ if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
m_data->minecraftProfile = MinecraftProfile();
finishActivity();
changeState(STATE_FAILED_HARD, tr("Minecraft Java profile response could not be parsed"));
@@ -784,6 +566,9 @@ void AuthContext::onMinecraftProfileDone(
}
if(m_data->type == AccountType::Mojang) {
+ auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
+ m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
+ m_data->minecraftEntitlement.ownsMinecraft = validProfile;
doMigrationEligibilityCheck();
}
else {
@@ -805,43 +590,13 @@ void AuthContext::doMigrationEligibilityCheck() {
requestor->get(request);
}
-bool parseRolloutResponse(QByteArray & data, bool& result) {
- qDebug() << "Parsing Rollout response...";
-#ifndef NDEBUG
- qDebug() << data;
-#endif
-
- QJsonParseError jsonError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: " << jsonError.errorString();
- return false;
- }
-
- auto obj = doc.object();
- QString feature;
- if(!getString(obj.value("feature"), feature)) {
- qWarning() << "Rollout feature is not a string";
- return false;
- }
- if(feature != "msamigration") {
- qWarning() << "Rollout feature is not what we expected (msamigration), but is instead \"" << feature << "\"";
- return false;
- }
- if(!getBool(obj.value("rollout"), result)) {
- qWarning() << "Rollout feature is not a string";
- return false;
- }
- return true;
-}
-
void AuthContext::onMigrationEligibilityCheckDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
if (error == QNetworkReply::NoError) {
- parseRolloutResponse(data, m_data->canMigrateToMSA);
+ Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
}
doGetSkin();
}
@@ -865,6 +620,11 @@ void AuthContext::onSkinDone(
if (error == QNetworkReply::NoError) {
m_data->minecraftProfile.skin.data = data;
}
+ succeed();
+
+}
+
+void AuthContext::succeed() {
m_data->validity_ = Katabasis::Validity::Certain;
finishActivity();
changeState(STATE_SUCCEEDED, tr("Finished all authentication steps"));
diff --git a/launcher/minecraft/auth/flows/AuthContext.h b/launcher/minecraft/auth/flows/AuthContext.h
index dc7552ac..5e4e9edc 100644
--- a/launcher/minecraft/auth/flows/AuthContext.h
+++ b/launcher/minecraft/auth/flows/AuthContext.h
@@ -7,7 +7,7 @@
#include <QNetworkReply>
#include <QImage>
-#include <katabasis/OAuth2.h>
+#include <katabasis/DeviceFlow.h>
#include "Yggdrasil.h"
#include "../AccountData.h"
#include "../AccountTask.h"
@@ -35,9 +35,6 @@ signals:
private slots:
// OAuth-specific callbacks
- void onOAuthLinkingSucceeded();
- void onOAuthLinkingFailed();
-
void onOAuthActivityChanged(Katabasis::Activity activity);
// Yggdrasil specific callbacks
@@ -63,6 +60,9 @@ protected:
void doXBoxProfile();
Q_SLOT void onXBoxProfileDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ void doEntitlements();
+ Q_SLOT void onEntitlementsDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+
void doMinecraftProfile();
Q_SLOT void onMinecraftProfileDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
@@ -72,6 +72,8 @@ protected:
void doGetSkin();
Q_SLOT void onSkinDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+ void succeed();
+
void failResult(bool & flag);
void succeedResult(bool & flag);
void checkResult();
@@ -82,12 +84,13 @@ protected:
void clearTokens();
protected:
- Katabasis::OAuth2 *m_oauth2 = nullptr;
+ Katabasis::DeviceFlow *m_oauth2 = nullptr;
Yggdrasil *m_yggdrasil = nullptr;
int m_requestsDone = 0;
bool m_xboxProfileSucceeded = false;
bool m_mcAuthSucceeded = false;
+ QString entitlementsRequestId;
QSet<int64_t> stsErrors;
bool stsFailed = false;
diff --git a/launcher/minecraft/auth/flows/AuthRequest.cpp b/launcher/minecraft/auth/flows/AuthRequest.cpp
index 77558fd3..82dba591 100644
--- a/launcher/minecraft/auth/flows/AuthRequest.cpp
+++ b/launcher/minecraft/auth/flows/AuthRequest.cpp
@@ -5,9 +5,9 @@
#include <QBuffer>
#include <QUrlQuery>
+#include "Application.h"
#include "AuthRequest.h"
#include "katabasis/Globals.h"
-#include "Env.h"
AuthRequest::AuthRequest(QObject *parent): QObject(parent) {
}
@@ -17,7 +17,7 @@ AuthRequest::~AuthRequest() {
void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
setup(req, QNetworkAccessManager::GetOperation);
- reply_ = ENV.qnam().get(request_);
+ reply_ = APPLICATION->network()->get(request_);
status_ = Requesting;
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
@@ -29,7 +29,7 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
setup(req, QNetworkAccessManager::PostOperation);
data_ = data;
status_ = Requesting;
- reply_ = ENV.qnam().post(request_, data_);
+ reply_ = APPLICATION->network()->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()));
diff --git a/launcher/minecraft/auth/flows/AuthRequest.h b/launcher/minecraft/auth/flows/AuthRequest.h
index 6a45a0bd..a547aea4 100644
--- a/launcher/minecraft/auth/flows/AuthRequest.h
+++ b/launcher/minecraft/auth/flows/AuthRequest.h
@@ -5,7 +5,6 @@
#include <QNetworkAccessManager>
#include <QUrl>
#include <QByteArray>
-#include <QHttpMultiPart>
#include "katabasis/Reply.h"
diff --git a/launcher/minecraft/auth/flows/MSAInteractive.cpp b/launcher/minecraft/auth/flows/MSAInteractive.cpp
index 03beb279..525aaf88 100644
--- a/launcher/minecraft/auth/flows/MSAInteractive.cpp
+++ b/launcher/minecraft/auth/flows/MSAInteractive.cpp
@@ -1,6 +1,9 @@
#include "MSAInteractive.h"
-MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthContext(data, parent) {}
+MSAInteractive::MSAInteractive(
+ AccountData* data,
+ QObject* parent
+) : AuthContext(data, parent) {}
void MSAInteractive::executeTask() {
m_requestsDone = 0;
@@ -14,7 +17,6 @@ void MSAInteractive::executeTask() {
m_oauth2->setExtraRequestParams(extraOpts);
beginActivity(Katabasis::Activity::LoggingIn);
- m_oauth2->unlink();
*m_data = AccountData();
- m_oauth2->link();
+ m_oauth2->login();
}
diff --git a/launcher/minecraft/auth/flows/MSAInteractive.h b/launcher/minecraft/auth/flows/MSAInteractive.h
index 9556f254..6654e0d6 100644
--- a/launcher/minecraft/auth/flows/MSAInteractive.h
+++ b/launcher/minecraft/auth/flows/MSAInteractive.h
@@ -5,6 +5,9 @@ class MSAInteractive : public AuthContext
{
Q_OBJECT
public:
- explicit MSAInteractive(AccountData * data, QObject *parent = 0);
+ explicit MSAInteractive(
+ AccountData *data,
+ QObject *parent = 0
+ );
void executeTask() override;
};
diff --git a/launcher/minecraft/auth/flows/MSASilent.h b/launcher/minecraft/auth/flows/MSASilent.h
index e1b3d43d..a442b49e 100644
--- a/launcher/minecraft/auth/flows/MSASilent.h
+++ b/launcher/minecraft/auth/flows/MSASilent.h
@@ -5,6 +5,9 @@ class MSASilent : public AuthContext
{
Q_OBJECT
public:
- explicit MSASilent(AccountData * data, QObject *parent = 0);
+ explicit MSASilent(
+ AccountData * data,
+ QObject *parent = 0
+ );
void executeTask() override;
};
diff --git a/launcher/minecraft/auth/flows/MojangLogin.cpp b/launcher/minecraft/auth/flows/MojangLogin.cpp
index cca911b5..6c217cd1 100644
--- a/launcher/minecraft/auth/flows/MojangLogin.cpp
+++ b/launcher/minecraft/auth/flows/MojangLogin.cpp
@@ -1,6 +1,10 @@
#include "MojangLogin.h"
-MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthContext(data, parent), m_password(password) {}
+MojangLogin::MojangLogin(
+ AccountData *data,
+ QString password,
+ QObject *parent
+): AuthContext(data, parent), m_password(password) {}
void MojangLogin::executeTask() {
m_requestsDone = 0;
diff --git a/launcher/minecraft/auth/flows/MojangLogin.h b/launcher/minecraft/auth/flows/MojangLogin.h
index 2e765ae8..5f33752f 100644
--- a/launcher/minecraft/auth/flows/MojangLogin.h
+++ b/launcher/minecraft/auth/flows/MojangLogin.h
@@ -5,7 +5,11 @@ class MojangLogin : public AuthContext
{
Q_OBJECT
public:
- explicit MojangLogin(AccountData * data, QString password, QObject *parent = 0);
+ explicit MojangLogin(
+ AccountData *data,
+ QString password,
+ QObject *parent = 0
+ );
void executeTask() override;
private:
diff --git a/launcher/minecraft/auth/flows/MojangRefresh.cpp b/launcher/minecraft/auth/flows/MojangRefresh.cpp
index af99175c..008c0453 100644
--- a/launcher/minecraft/auth/flows/MojangRefresh.cpp
+++ b/launcher/minecraft/auth/flows/MojangRefresh.cpp
@@ -1,6 +1,9 @@
#include "MojangRefresh.h"
-MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthContext(data, parent) {}
+MojangRefresh::MojangRefresh(
+ AccountData *data,
+ QObject *parent
+) : AuthContext(data, parent) {}
void MojangRefresh::executeTask() {
m_requestsDone = 0;
diff --git a/launcher/minecraft/auth/flows/MojangRefresh.h b/launcher/minecraft/auth/flows/MojangRefresh.h
index fb4facd5..06e4e4ce 100644
--- a/launcher/minecraft/auth/flows/MojangRefresh.h
+++ b/launcher/minecraft/auth/flows/MojangRefresh.h
@@ -5,6 +5,6 @@ class MojangRefresh : public AuthContext
{
Q_OBJECT
public:
- explicit MojangRefresh(AccountData * data, QObject *parent = 0);
+ explicit MojangRefresh(AccountData *data, QObject *parent = 0);
void executeTask() override;
};
diff --git a/launcher/minecraft/auth/flows/Parsers.cpp b/launcher/minecraft/auth/flows/Parsers.cpp
new file mode 100644
index 00000000..ecb11cf9
--- /dev/null
+++ b/launcher/minecraft/auth/flows/Parsers.cpp
@@ -0,0 +1,316 @@
+#include "Parsers.h"
+
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QDebug>
+
+namespace Parsers {
+
+bool getDateTime(QJsonValue value, QDateTime & out) {
+ if(!value.isString()) {
+ return false;
+ }
+ out = QDateTime::fromString(value.toString(), Qt::ISODate);
+ return out.isValid();
+}
+
+bool getString(QJsonValue value, QString & out) {
+ if(!value.isString()) {
+ return false;
+ }
+ out = value.toString();
+ return true;
+}
+
+bool getNumber(QJsonValue value, double & out) {
+ if(!value.isDouble()) {
+ return false;
+ }
+ out = value.toDouble();
+ 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()) {
+ return false;
+ }
+ out = value.toBool();
+ return true;
+}
+
+/*
+{
+ "IssueInstant":"2020-12-07T19:52:08.4463796Z",
+ "NotAfter":"2020-12-21T19:52:08.4463796Z",
+ "Token":"token",
+ "DisplayClaims":{
+ "xui":[
+ {
+ "uhs":"userhash"
+ }
+ ]
+ }
+ }
+*/
+// TODO: handle error responses ...
+/*
+{
+ "Identity":"0",
+ "XErr":2148916238,
+ "Message":"",
+ "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily"
+}
+// 2148916233 = missing XBox account
+// 2148916238 = child account not linked to a family
+*/
+
+bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, const char * name) {
+ qDebug() << "Parsing" << name <<":";
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ auto obj = doc.object();
+ if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
+ qWarning() << "User IssueInstant is not a timestamp";
+ return false;
+ }
+ if(!getDateTime(obj.value("NotAfter"), output.notAfter)) {
+ qWarning() << "User NotAfter is not a timestamp";
+ return false;
+ }
+ if(!getString(obj.value("Token"), output.token)) {
+ qWarning() << "User Token is not a timestamp";
+ return false;
+ }
+ auto arrayVal = obj.value("DisplayClaims").toObject().value("xui");
+ if(!arrayVal.isArray()) {
+ qWarning() << "Missing xui claims array";
+ return false;
+ }
+ bool foundUHS = false;
+ for(auto item: arrayVal.toArray()) {
+ if(!item.isObject()) {
+ continue;
+ }
+ auto obj = item.toObject();
+ if(obj.contains("uhs")) {
+ foundUHS = true;
+ } else {
+ continue;
+ }
+ // consume all 'display claims' ... whatever that means
+ for(auto iter = obj.begin(); iter != obj.end(); iter++) {
+ QString claim;
+ if(!getString(obj.value(iter.key()), claim)) {
+ qWarning() << "display claim " << iter.key() << " is not a string...";
+ return false;
+ }
+ output.extra[iter.key()] = claim;
+ }
+
+ break;
+ }
+ if(!foundUHS) {
+ qWarning() << "Missing uhs";
+ return false;
+ }
+ output.validity = Katabasis::Validity::Certain;
+ qDebug() << name << "is valid.";
+ return true;
+}
+
+bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
+ qDebug() << "Parsing Minecraft profile...";
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ auto obj = doc.object();
+ if(!getString(obj.value("id"), output.id)) {
+ qWarning() << "Minecraft profile id is not a string";
+ return false;
+ }
+
+ if(!getString(obj.value("name"), output.name)) {
+ qWarning() << "Minecraft profile name is not a string";
+ return false;
+ }
+
+ auto skinsArray = obj.value("skins").toArray();
+ for(auto skin: skinsArray) {
+ auto skinObj = skin.toObject();
+ Skin skinOut;
+ if(!getString(skinObj.value("id"), skinOut.id)) {
+ continue;
+ }
+ QString state;
+ if(!getString(skinObj.value("state"), state)) {
+ continue;
+ }
+ if(state != "ACTIVE") {
+ continue;
+ }
+ if(!getString(skinObj.value("url"), skinOut.url)) {
+ continue;
+ }
+ if(!getString(skinObj.value("variant"), skinOut.variant)) {
+ continue;
+ }
+ // we deal with only the active skin
+ output.skin = skinOut;
+ break;
+ }
+ auto capesArray = obj.value("capes").toArray();
+
+ QString currentCape;
+ for(auto cape: capesArray) {
+ auto capeObj = cape.toObject();
+ Cape capeOut;
+ if(!getString(capeObj.value("id"), capeOut.id)) {
+ continue;
+ }
+ QString state;
+ if(!getString(capeObj.value("state"), state)) {
+ continue;
+ }
+ if(state == "ACTIVE") {
+ currentCape = capeOut.id;
+ }
+ if(!getString(capeObj.value("url"), capeOut.url)) {
+ continue;
+ }
+ if(!getString(capeObj.value("alias"), capeOut.alias)) {
+ continue;
+ }
+
+ output.capes[capeOut.id] = capeOut;
+ }
+ output.currentCape = currentCape;
+ output.validity = Katabasis::Validity::Certain;
+ return true;
+}
+
+bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
+ qDebug() << "Parsing Minecraft entitlements...";
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ auto obj = doc.object();
+
+ auto itemsArray = obj.value("items").toArray();
+ for(auto item: itemsArray) {
+ auto itemObj = item.toObject();
+ QString name;
+ if(!getString(itemObj.value("name"), name)) {
+ continue;
+ }
+ if(name == "game_minecraft") {
+ output.canPlayMinecraft = true;
+ }
+ if(name == "product_minecraft") {
+ output.ownsMinecraft = true;
+ }
+ }
+ output.validity = Katabasis::Validity::Certain;
+ return true;
+}
+
+bool parseRolloutResponse(QByteArray & data, bool& result) {
+ qDebug() << "Parsing Rollout response...";
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ auto obj = doc.object();
+ QString feature;
+ if(!getString(obj.value("feature"), feature)) {
+ qWarning() << "Rollout feature is not a string";
+ return false;
+ }
+ if(feature != "msamigration") {
+ qWarning() << "Rollout feature is not what we expected (msamigration), but is instead \"" << feature << "\"";
+ return false;
+ }
+ if(!getBool(obj.value("rollout"), result)) {
+ qWarning() << "Rollout feature is not a string";
+ return false;
+ }
+ return true;
+}
+
+bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
+ QJsonParseError jsonError;
+ qDebug() << "Parsing Mojang response...";
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ auto obj = doc.object();
+ double expires_in = 0;
+ if(!getNumber(obj.value("expires_in"), expires_in)) {
+ qWarning() << "expires_in is not a valid number";
+ return false;
+ }
+ auto currentTime = QDateTime::currentDateTimeUtc();
+ output.issueInstant = currentTime;
+ output.notAfter = currentTime.addSecs(expires_in);
+
+ QString username;
+ if(!getString(obj.value("username"), username)) {
+ qWarning() << "username is not valid";
+ return false;
+ }
+
+ // TODO: it's a JWT... validate it?
+ if(!getString(obj.value("access_token"), output.token)) {
+ qWarning() << "access_token is not valid";
+ return false;
+ }
+ output.validity = Katabasis::Validity::Certain;
+ qDebug() << "Mojang response is valid.";
+ return true;
+}
+
+}
diff --git a/launcher/minecraft/auth/flows/Parsers.h b/launcher/minecraft/auth/flows/Parsers.h
new file mode 100644
index 00000000..b484a073
--- /dev/null
+++ b/launcher/minecraft/auth/flows/Parsers.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "../AccountData.h"
+
+namespace Parsers
+{
+ bool getDateTime(QJsonValue value, QDateTime & out);
+ bool getString(QJsonValue value, QString & out);
+ bool getNumber(QJsonValue value, double & out);
+ bool getNumber(QJsonValue value, int64_t & out);
+ bool getBool(QJsonValue value, bool & out);
+
+ bool parseXTokenResponse(QByteArray &data, Katabasis::Token &output, const char * name);
+ bool parseMojangResponse(QByteArray &data, Katabasis::Token &output);
+
+ bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
+ bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
+ bool parseRolloutResponse(QByteArray &data, bool& result);
+}
diff --git a/launcher/minecraft/auth/flows/Yggdrasil.cpp b/launcher/minecraft/auth/flows/Yggdrasil.cpp
index 20ca63d0..5ea168e8 100644
--- a/launcher/minecraft/auth/flows/Yggdrasil.cpp
+++ b/launcher/minecraft/auth/flows/Yggdrasil.cpp
@@ -23,12 +23,10 @@
#include <QNetworkReply>
#include <QByteArray>
-#include <Env.h>
-
-#include <BuildConfig.h>
-
#include <QDebug>
+#include "Application.h"
+
Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
: AccountTask(data, parent)
{
@@ -40,7 +38,7 @@ void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
QNetworkRequest netRequest(endpoint);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- m_netReply = ENV.qnam().post(netRequest, content);
+ m_netReply = APPLICATION->network()->post(netRequest, content);
connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply);
connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers);
connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers);
@@ -86,7 +84,7 @@ void Yggdrasil::refresh() {
req.insert("requestUser", false);
QJsonDocument doc(req);
- QUrl reqUrl(BuildConfig.AUTH_BASE + "refresh");
+ QUrl reqUrl("https://authserver.mojang.com/refresh");
QByteArray requestData = doc.toJson();
sendRequest(reqUrl, requestData);
@@ -131,7 +129,7 @@ void Yggdrasil::login(QString password) {
QJsonDocument doc(req);
- QUrl reqUrl(BuildConfig.AUTH_BASE + "authenticate");
+ QUrl reqUrl("https://authserver.mojang.com/authenticate");
QNetworkRequest netRequest(reqUrl);
QByteArray requestData = doc.toJson();
@@ -140,20 +138,18 @@ void Yggdrasil::login(QString password) {
-void Yggdrasil::refreshTimers(qint64, qint64)
-{
+void Yggdrasil::refreshTimers(qint64, qint64) {
timeout_keeper.stop();
timeout_keeper.start(timeout_max);
progress(count = 0, timeout_max);
}
-void Yggdrasil::heartbeat()
-{
+
+void Yggdrasil::heartbeat() {
count += time_step;
progress(count, timeout_max);
}
-bool Yggdrasil::abort()
-{
+bool Yggdrasil::abort() {
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = Yggdrasil::BY_USER;
@@ -161,19 +157,16 @@ bool Yggdrasil::abort()
return true;
}
-void Yggdrasil::abortByTimeout()
-{
+void Yggdrasil::abortByTimeout() {
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = Yggdrasil::BY_TIMEOUT;
m_netReply->abort();
}
-void Yggdrasil::sslErrors(QList<QSslError> errors)
-{
+void Yggdrasil::sslErrors(QList<QSslError> errors) {
int i = 1;
- for (auto error : errors)
- {
+ for (auto error : errors) {
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
@@ -181,8 +174,7 @@ void Yggdrasil::sslErrors(QList<QSslError> errors)
}
}
-void Yggdrasil::processResponse(QJsonObject responseData)
-{
+void Yggdrasil::processResponse(QJsonObject responseData) {
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
@@ -191,8 +183,7 @@ void Yggdrasil::processResponse(QJsonObject responseData)
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
QString clientToken = responseData.value("clientToken").toString("");
- if (clientToken.isEmpty())
- {
+ if (clientToken.isEmpty()) {
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
@@ -208,8 +199,7 @@ void Yggdrasil::processResponse(QJsonObject responseData)
// Now, we set the access token.
qDebug() << "Getting access token.";
QString accessToken = responseData.value("accessToken").toString("");
- if (accessToken.isEmpty())
- {
+ if (accessToken.isEmpty()) {
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
@@ -217,6 +207,7 @@ void Yggdrasil::processResponse(QJsonObject responseData)
// Set the access token.
m_data->yggdrasilToken.token = accessToken;
m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
+ m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
@@ -224,8 +215,7 @@ void Yggdrasil::processResponse(QJsonObject responseData)
changeState(STATE_SUCCEEDED);
}
-void Yggdrasil::processReply()
-{
+void Yggdrasil::processReply() {
changeState(STATE_WORKING);
switch (m_netReply->error())
@@ -247,9 +237,9 @@ void Yggdrasil::processReply()
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
"<li>Some device on your network is interfering with SSL traffic. In that case, "
"you have bigger worries than Minecraft not starting.</li>"
- "<li>Possibly something else. Check the %1 log file for details</li>"
+ "<li>Possibly something else. Check the log file for details</li>"
"</ul>"
- ).arg(BuildConfig.LAUNCHER_NAME)
+ )
);
return;
// used for invalid credentials and similar errors. Fall through.
@@ -278,19 +268,16 @@ void Yggdrasil::processReply()
// Check the response code.
int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- if (responseCode == 200)
- {
+ if (responseCode == 200) {
// If the response code was 200, then there shouldn't be an error. Make sure
// anyways.
// Also, sometimes an empty reply indicates success. If there was no data received,
// pass an empty json object to the processResponse function.
- if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
- {
+ if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
return;
}
- else
- {
+ else {
changeState(
STATE_FAILED_SOFT,
tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)
@@ -304,16 +291,14 @@ void Yggdrasil::processReply()
// about the error.
// If we can parse the response, then get information from it. Otherwise just say
// there was an unknown error.
- if (jsonError.error == QJsonParseError::NoError)
- {
+ if (jsonError.error == QJsonParseError::NoError) {
// We were able to parse the server's response. Woo!
// Call processError. If a subclass has overridden it then they'll handle their
// stuff there.
qDebug() << "The request failed, but the server gave us an error message. Processing error.";
processError(doc.object());
}
- else
- {
+ else {
// The server didn't say anything regarding the error. Give the user an unknown
// error.
qDebug() << "The request failed and the server gave no error message. Unknown error.";
@@ -324,14 +309,12 @@ void Yggdrasil::processReply()
}
}
-void Yggdrasil::processError(QJsonObject responseData)
-{
+void Yggdrasil::processError(QJsonObject responseData) {
QJsonValue errorVal = responseData.value("error");
QJsonValue errorMessageValue = responseData.value("errorMessage");
QJsonValue causeVal = responseData.value("cause");
- if (errorVal.isString() && errorMessageValue.isString())
- {
+ if (errorVal.isString() && errorMessageValue.isString()) {
m_error = std::shared_ptr<Error>(
new Error {
errorVal.toString(""),
@@ -341,8 +324,7 @@ void Yggdrasil::processError(QJsonObject responseData)
);
changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
}
- else
- {
+ else {
// Error is not in standard format. Don't set m_error and return unknown error.
changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
}
diff --git a/launcher/minecraft/auth/flows/Yggdrasil.h b/launcher/minecraft/auth/flows/Yggdrasil.h
index e709cb9f..b9670ec7 100644
--- a/launcher/minecraft/auth/flows/Yggdrasil.h
+++ b/launcher/minecraft/auth/flows/Yggdrasil.h
@@ -24,6 +24,7 @@
#include "../MinecraftAccount.h"
+class QNetworkAccessManager;
class QNetworkReply;
/**
@@ -33,7 +34,10 @@ class Yggdrasil : public AccountTask
{
Q_OBJECT
public:
- explicit Yggdrasil(AccountData * data, QObject *parent = 0);
+ explicit Yggdrasil(
+ AccountData *data,
+ QObject *parent = 0
+ );
virtual ~Yggdrasil() {};
void refresh();