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.cpp671
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.h110
-rw-r--r--launcher/minecraft/auth/flows/AuthFlow.cpp71
-rw-r--r--launcher/minecraft/auth/flows/AuthFlow.h45
-rw-r--r--launcher/minecraft/auth/flows/AuthRequest.cpp121
-rw-r--r--launcher/minecraft/auth/flows/AuthRequest.h64
-rw-r--r--launcher/minecraft/auth/flows/MSA.cpp37
-rw-r--r--launcher/minecraft/auth/flows/MSA.h22
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.cpp22
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.h13
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.cpp16
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.h13
-rw-r--r--launcher/minecraft/auth/flows/Mojang.cpp27
-rw-r--r--launcher/minecraft/auth/flows/Mojang.h26
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.cpp18
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.h17
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.cpp17
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.h10
-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.cpp331
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.h86
22 files changed, 228 insertions, 1844 deletions
diff --git a/launcher/minecraft/auth/flows/AuthContext.cpp b/launcher/minecraft/auth/flows/AuthContext.cpp
deleted file mode 100644
index 00957fd4..00000000
--- a/launcher/minecraft/auth/flows/AuthContext.cpp
+++ /dev/null
@@ -1,671 +0,0 @@
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-#include <QDesktopServices>
-#include <QMetaEnum>
-#include <QDebug>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
-#include <QUuid>
-#include <QUrlQuery>
-
-#include "AuthContext.h"
-#include "katabasis/Globals.h"
-#include "AuthRequest.h"
-
-#include "Parsers.h"
-
-#include <Application.h>
-
-using OAuth2 = Katabasis::DeviceFlow;
-using Activity = Katabasis::Activity;
-
-AuthContext::AuthContext(AccountData * data, QObject *parent) :
- AccountTask(data, parent)
-{
-}
-
-void AuthContext::beginActivity(Activity activity) {
- if(isBusy()) {
- throw 0;
- }
- m_activity = activity;
- changeState(STATE_WORKING, "Initializing");
- emit activityChanged(m_activity);
-}
-
-void AuthContext::finishActivity() {
- if(!isBusy()) {
- throw 0;
- }
- m_activity = Katabasis::Activity::Idle;
- setStage(AuthStage::Complete);
- m_data->validity_ = m_data->minecraftProfile.validity;
- emit activityChanged(m_activity);
-}
-
-void AuthContext::initMSA() {
- if(m_oauth2) {
- return;
- }
-
- OAuth2::Options opts;
- opts.scope = "XboxLive.signin offline_access";
- 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";
-
- // 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::activityChanged, this, &AuthContext::onOAuthActivityChanged);
- connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &AuthContext::showVerificationUriAndCode);
-}
-
-void AuthContext::initMojang() {
- if(m_yggdrasil) {
- return;
- }
- m_yggdrasil = new Yggdrasil(m_data, this);
-
- connect(m_yggdrasil, &Task::failed, this, &AuthContext::onMojangFailed);
- connect(m_yggdrasil, &Task::succeeded, this, &AuthContext::onMojangSucceeded);
-}
-
-void AuthContext::onMojangSucceeded() {
- doMinecraftProfile();
-}
-
-
-void AuthContext::onMojangFailed() {
- finishActivity();
- m_error = m_yggdrasil->m_error;
- m_aborted = m_yggdrasil->m_aborted;
- changeState(m_yggdrasil->accountState(), tr("Mojang user authentication failed."));
-}
-
-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();
- 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::doUserAuth() {
- setStage(AuthStage::UserAuth);
- changeState(STATE_WORKING, tr("Starting user authentication"));
-
- 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";
- auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
-
- QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Accept", "application/json");
- auto *requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &AuthContext::onUserAuthDone);
- requestor->post(request, xbox_auth_data.toUtf8());
- qDebug() << "First layer of XBox auth ... commencing.";
-}
-
-void AuthContext::onUserAuthDone(
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- finishActivity();
- changeState(STATE_FAILED_HARD, tr("XBox user authentication failed."));
- return;
- }
-
- Katabasis::Token temp;
- 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."));
- return;
- }
- m_data->userToken = temp;
-
- setStage(AuthStage::XboxAuth);
- changeState(STATE_WORKING, tr("Starting XBox authentication"));
-
- 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",
- },
- }
-*/
-void AuthContext::doSTSAuthMinecraft() {
- QString xbox_auth_template = R"XXX(
-{
- "Properties": {
- "SandboxId": "RETAIL",
- "UserTokens": [
- "%1"
- ]
- },
- "RelyingParty": "rp://api.minecraftservices.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");
- AuthRequest *requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &AuthContext::onSTSAuthMinecraftDone);
- requestor->post(request, xbox_auth_data.toUtf8());
- 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(!Parsers::getNumber(obj.value("XErr"), errorCode)) {
- qWarning() << "XErr is not a number";
- return;
- }
- stsErrors.insert(errorCode);
- stsFailed = true;
- }
-}
-
-
-void AuthContext::onSTSAuthMinecraftDone(
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
-#ifndef NDEBUG
- qDebug() << replyData;
-#endif
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- processSTSError(error, replyData, headers);
- failResult(m_mcAuthSucceeded);
- return;
- }
-
- Katabasis::Token temp;
- if(!Parsers::parseXTokenResponse(replyData, temp, "STSAuthMinecraft")) {
- qWarning() << "Could not parse authorization response for access to mojang services...";
- failResult(m_mcAuthSucceeded);
- return;
- }
-
- if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
- qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
- failResult(m_mcAuthSucceeded);
- return;
- }
- m_data->mojangservicesToken = temp;
-
- doMinecraftAuth();
-}
-
-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(
-{
- "xtoken": "XBL3.0 x=%1;%2",
- "platform": "PC_LAUNCHER"
-}
-)XXX";
- auto requestBody = mc_auth_template.arg(uhs, xToken);
-
- 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, requestBody.toUtf8());
- qDebug() << "Getting Minecraft access token...";
-}
-
-void AuthContext::onMinecraftAuthDone(
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- qDebug() << replyData;
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
-#ifndef NDEBUG
- qDebug() << replyData;
-#endif
- failResult(m_mcAuthSucceeded);
- return;
- }
-
- if(!Parsers::parseMojangResponse(replyData, m_data->yggdrasilToken)) {
- qWarning() << "Could not parse login_with_xbox response...";
-#ifndef NDEBUG
- qDebug() << replyData;
-#endif
- failResult(m_mcAuthSucceeded);
- return;
- }
-
- 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");
- AuthRequest *requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::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(!Parsers::parseXTokenResponse(replyData, temp, "STSAuthGeneric")) {
- 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() {
- 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");
- request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
- AuthRequest *requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &AuthContext::onXBoxProfileDone);
- requestor->get(request);
- qDebug() << "Getting Xbox profile...";
-}
-
-void AuthContext::onXBoxProfileDone(
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
-#ifndef NDEBUG
- qDebug() << replyData;
-#endif
- failResult(m_xboxProfileSucceeded);
- return;
- }
-
-#ifndef NDEBUG
- qDebug() << "XBox profile: " << replyData;
-#endif
-
- succeedResult(m_xboxProfileSucceeded);
-}
-
-void AuthContext::succeedResult(bool& flag) {
- m_requestsDone ++;
- flag = true;
- checkResult();
-}
-
-void AuthContext::failResult(bool& flag) {
- m_requestsDone ++;
- flag = false;
- checkResult();
-}
-
-void AuthContext::checkResult() {
- qDebug() << "AuthContext::checkResult called";
- if(m_requestsDone != 2) {
- qDebug() << "Number of ready results:" << m_requestsDone;
- return;
- }
- if(m_mcAuthSucceeded && m_xboxProfileSucceeded) {
- doEntitlements();
- }
- else {
- finishActivity();
- 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/4403181904525\">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"));
- }
- }
-}
-
-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
- // TODO: check presence of same entitlementsRequestId?
- // TODO: validate JWTs?
- Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement);
- doMinecraftProfile();
-}
-
-void AuthContext::doMinecraftProfile() {
- setStage(AuthStage::MinecraftProfile);
- changeState(STATE_WORKING, tr("Starting minecraft profile acquisition"));
-
- auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
- 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::onMinecraftProfileDone);
- requestor->get(request);
-}
-
-void AuthContext::onMinecraftProfileDone(
- QNetworkReply::NetworkError error,
- QByteArray data,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
-#ifndef NDEBUG
- 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();
- succeed();
- return;
- }
- if (error != QNetworkReply::NoError) {
- finishActivity();
- changeState(STATE_FAILED_HARD, tr("Minecraft Java profile acquisition failed."));
- return;
- }
- 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"));
- return;
- }
-
- 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 {
- 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());
-
- AuthRequest *requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &AuthContext::onMigrationEligibilityCheckDone);
- requestor->get(request);
-}
-
-void AuthContext::onMigrationEligibilityCheckDone(
- QNetworkReply::NetworkError error,
- QByteArray data,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error == QNetworkReply::NoError) {
- Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
- }
- doGetSkin();
-}
-
-void AuthContext::doGetSkin() {
- setStage(AuthStage::Skin);
- changeState(STATE_WORKING, tr("Fetching player skin"));
-
- auto url = QUrl(m_data->minecraftProfile.skin.url);
- QNetworkRequest request = QNetworkRequest(url);
- AuthRequest *requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &AuthContext::onSkinDone);
- requestor->get(request);
-}
-
-void AuthContext::onSkinDone(
- QNetworkReply::NetworkError error,
- QByteArray data,
- QList<QNetworkReply::RawHeaderPair>
-) {
- 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"));
-}
-
-void AuthContext::setStage(AuthContext::AuthStage stage) {
- m_stage = stage;
- emit progress((int)m_stage, (int)AuthStage::Complete);
-}
-
-
-QString AuthContext::getStateMessage() const {
- switch (m_accountState)
- {
- case STATE_WORKING:
- switch(m_stage) {
- case AuthStage::Initial: {
- QString loginMessage = tr("Logging in as %1 user");
- if(m_data->type == AccountType::MSA) {
- return loginMessage.arg("Microsoft");
- }
- else {
- return loginMessage.arg("Mojang");
- }
- }
- case AuthStage::UserAuth:
- return tr("Logging in as XBox user");
- case AuthStage::XboxAuth:
- return tr("Logging in with XBox and Mojang services");
- case AuthStage::MinecraftProfile:
- return tr("Getting Minecraft profile");
- case AuthStage::MigrationEligibility:
- return tr("Checking for migration eligibility");
- case AuthStage::Skin:
- return tr("Getting Minecraft skin");
- case AuthStage::Complete:
- return tr("Finished");
- default:
- break;
- }
- default:
- return AccountTask::getStateMessage();
- }
-}
diff --git a/launcher/minecraft/auth/flows/AuthContext.h b/launcher/minecraft/auth/flows/AuthContext.h
deleted file mode 100644
index 5e4e9edc..00000000
--- a/launcher/minecraft/auth/flows/AuthContext.h
+++ /dev/null
@@ -1,110 +0,0 @@
-#pragma once
-
-#include <QObject>
-#include <QList>
-#include <QVector>
-#include <QSet>
-#include <QNetworkReply>
-#include <QImage>
-
-#include <katabasis/DeviceFlow.h>
-#include "Yggdrasil.h"
-#include "../AccountData.h"
-#include "../AccountTask.h"
-
-class AuthContext : public AccountTask
-{
- Q_OBJECT
-
-public:
- explicit AuthContext(AccountData * data, QObject *parent = 0);
-
- bool isBusy() {
- return m_activity != Katabasis::Activity::Idle;
- };
- Katabasis::Validity validity() {
- return m_data->validity_;
- };
-
- //bool signOut();
-
- QString getStateMessage() const override;
-
-signals:
- void activityChanged(Katabasis::Activity activity);
-
-private slots:
-// OAuth-specific callbacks
- void onOAuthActivityChanged(Katabasis::Activity activity);
-
-// Yggdrasil specific callbacks
- void onMojangSucceeded();
- void onMojangFailed();
-
-protected:
- void initMSA();
- void initMojang();
-
- void doUserAuth();
- Q_SLOT void onUserAuthDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void processSTSError(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doSTSAuthMinecraft();
- Q_SLOT void onSTSAuthMinecraftDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
- void doMinecraftAuth();
- Q_SLOT void onMinecraftAuthDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doSTSAuthGeneric();
- Q_SLOT void onSTSAuthGenericDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
- 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>);
-
- void doMigrationEligibilityCheck();
- Q_SLOT void onMigrationEligibilityCheckDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doGetSkin();
- Q_SLOT void onSkinDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void succeed();
-
- void failResult(bool & flag);
- void succeedResult(bool & flag);
- void checkResult();
-
-protected:
- void beginActivity(Katabasis::Activity activity);
- void finishActivity();
- void clearTokens();
-
-protected:
- 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;
-
- Katabasis::Activity m_activity = Katabasis::Activity::Idle;
- enum class AuthStage {
- Initial,
- UserAuth,
- XboxAuth,
- MinecraftProfile,
- MigrationEligibility,
- Skin,
- Complete
- } m_stage = AuthStage::Initial;
-
- void setStage(AuthStage stage);
-};
diff --git a/launcher/minecraft/auth/flows/AuthFlow.cpp b/launcher/minecraft/auth/flows/AuthFlow.cpp
new file mode 100644
index 00000000..4f78e8c3
--- /dev/null
+++ b/launcher/minecraft/auth/flows/AuthFlow.cpp
@@ -0,0 +1,71 @@
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QDebug>
+
+#include "AuthFlow.h"
+#include "katabasis/Globals.h"
+
+#include <Application.h>
+
+AuthFlow::AuthFlow(AccountData * data, QObject *parent) :
+ AccountTask(data, parent)
+{
+}
+
+void AuthFlow::succeed() {
+ m_data->validity_ = Katabasis::Validity::Certain;
+ changeState(
+ AccountTaskState::STATE_SUCCEEDED,
+ tr("Finished all authentication steps")
+ );
+}
+
+void AuthFlow::executeTask() {
+ if(m_currentStep) {
+ return;
+ }
+ changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
+ nextStep();
+}
+
+void AuthFlow::nextStep() {
+ if(m_steps.size() == 0) {
+ // we got to the end without an incident... assume this is all.
+ m_currentStep.reset();
+ succeed();
+ return;
+ }
+ m_currentStep = m_steps.front();
+ qDebug() << "AuthFlow:" << m_currentStep->describe();
+ m_steps.pop_front();
+ connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished);
+ connect(m_currentStep.get(), &AuthStep::showVerificationUriAndCode, this, &AuthFlow::showVerificationUriAndCode);
+ connect(m_currentStep.get(), &AuthStep::hideVerificationUriAndCode, this, &AuthFlow::hideVerificationUriAndCode);
+
+ m_currentStep->perform();
+}
+
+
+QString AuthFlow::getStateMessage() const {
+ switch (m_taskState)
+ {
+ case AccountTaskState::STATE_WORKING: {
+ if(m_currentStep) {
+ return m_currentStep->describe();
+ }
+ else {
+ return tr("Working...");
+ }
+ }
+ default: {
+ return AccountTask::getStateMessage();
+ }
+ }
+}
+
+void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) {
+ if(changeState(resultingState, message)) {
+ nextStep();
+ }
+}
diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/flows/AuthFlow.h
new file mode 100644
index 00000000..e067cc99
--- /dev/null
+++ b/launcher/minecraft/auth/flows/AuthFlow.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <QObject>
+#include <QList>
+#include <QVector>
+#include <QSet>
+#include <QNetworkReply>
+#include <QImage>
+
+#include <katabasis/DeviceFlow.h>
+
+#include "minecraft/auth/Yggdrasil.h"
+#include "minecraft/auth/AccountData.h"
+#include "minecraft/auth/AccountTask.h"
+#include "minecraft/auth/AuthStep.h"
+
+class AuthFlow : public AccountTask
+{
+ Q_OBJECT
+
+public:
+ explicit AuthFlow(AccountData * data, QObject *parent = 0);
+
+ Katabasis::Validity validity() {
+ return m_data->validity_;
+ };
+
+ QString getStateMessage() const override;
+
+ void executeTask() override;
+
+signals:
+ void activityChanged(Katabasis::Activity activity);
+
+private slots:
+ void stepFinished(AccountTaskState resultingState, QString message);
+
+protected:
+ void succeed();
+ void nextStep();
+
+protected:
+ QList<AuthStep::Ptr> m_steps;
+ AuthStep::Ptr m_currentStep;
+};
diff --git a/launcher/minecraft/auth/flows/AuthRequest.cpp b/launcher/minecraft/auth/flows/AuthRequest.cpp
deleted file mode 100644
index 82dba591..00000000
--- a/launcher/minecraft/auth/flows/AuthRequest.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-#include <cassert>
-
-#include <QDebug>
-#include <QTimer>
-#include <QBuffer>
-#include <QUrlQuery>
-
-#include "Application.h"
-#include "AuthRequest.h"
-#include "katabasis/Globals.h"
-
-AuthRequest::AuthRequest(QObject *parent): QObject(parent) {
-}
-
-AuthRequest::~AuthRequest() {
-}
-
-void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
- setup(req, QNetworkAccessManager::GetOperation);
- 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)));
- 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_ = 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()));
- connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
- connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64)));
-}
-
-void AuthRequest::onRequestFinished() {
- if (status_ == Idle) {
- return;
- }
- if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
- return;
- }
- finish();
-}
-
-void AuthRequest::onRequestError(QNetworkReply::NetworkError error) {
- qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
- if (status_ == Idle) {
- return;
- }
- if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
- return;
- }
- qWarning() << "AuthRequest::onRequestError: Error string: " << reply_->errorString();
- int httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
- error_ = error;
-
- // QTimer::singleShot(10, this, SLOT(finish()));
-}
-
-void AuthRequest::onSslErrors(QList<QSslError> errors) {
- int i = 1;
- for (auto error : errors) {
- qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
- auto cert = error.certificate();
- qCritical() << "Certificate in question:\n" << cert.toText();
- i++;
- }
-}
-
-void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total) {
- if (status_ == Idle) {
- qWarning() << "AuthRequest::onUploadProgress: No pending request";
- return;
- }
- if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
- return;
- }
- // Restart timeout because request in progress
- Katabasis::Reply *o2Reply = timedReplies_.find(reply_);
- if(o2Reply) {
- o2Reply->start();
- }
- emit uploadProgress(uploaded, total);
-}
-
-void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) {
- request_ = req;
- operation_ = operation;
- url_ = req.url();
-
- QUrl url = url_;
- request_.setUrl(url);
-
- if (!verb.isEmpty()) {
- request_.setRawHeader(Katabasis::HTTP_HTTP_HEADER, verb);
- }
-
- status_ = Requesting;
- error_ = QNetworkReply::NoError;
-}
-
-void AuthRequest::finish() {
- QByteArray data;
- if (status_ == Idle) {
- qWarning() << "AuthRequest::finish: No pending request";
- return;
- }
- data = reply_->readAll();
- status_ = Idle;
- timedReplies_.remove(reply_);
- reply_->disconnect(this);
- reply_->deleteLater();
- QList<QNetworkReply::RawHeaderPair> headers = reply_->rawHeaderPairs();
- emit finished(error_, data, headers);
-}
diff --git a/launcher/minecraft/auth/flows/AuthRequest.h b/launcher/minecraft/auth/flows/AuthRequest.h
deleted file mode 100644
index a547aea4..00000000
--- a/launcher/minecraft/auth/flows/AuthRequest.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma once
-#include <QObject>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-#include <QNetworkAccessManager>
-#include <QUrl>
-#include <QByteArray>
-
-#include "katabasis/Reply.h"
-
-/// Makes authentication requests.
-class AuthRequest: public QObject {
- Q_OBJECT
-
-public:
- explicit AuthRequest(QObject *parent = 0);
- ~AuthRequest();
-
-public slots:
- void get(const QNetworkRequest &req, int timeout = 60*1000);
- void post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
-
-
-signals:
-
- /// Emitted when a request has been completed or failed.
- void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
-
- /// Emitted when an upload has progressed.
- void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
-
-protected slots:
-
- /// Handle request finished.
- void onRequestFinished();
-
- /// Handle request error.
- void onRequestError(QNetworkReply::NetworkError error);
-
- /// Handle ssl errors.
- void onSslErrors(QList<QSslError> errors);
-
- /// Finish the request, emit finished() signal.
- void finish();
-
- /// Handle upload progress.
- void onUploadProgress(qint64 uploaded, qint64 total);
-
-protected:
- void setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray());
-
- enum Status {
- Idle, Requesting, ReRequesting
- };
-
- QNetworkRequest request_;
- QByteArray data_;
- QNetworkReply *reply_;
- Status status_;
- QNetworkAccessManager::Operation operation_;
- QUrl url_;
- Katabasis::ReplyList timedReplies_;
- QNetworkReply::NetworkError error_;
-};
diff --git a/launcher/minecraft/auth/flows/MSA.cpp b/launcher/minecraft/auth/flows/MSA.cpp
new file mode 100644
index 00000000..416b8f2c
--- /dev/null
+++ b/launcher/minecraft/auth/flows/MSA.cpp
@@ -0,0 +1,37 @@
+#include "MSA.h"
+
+#include "minecraft/auth/steps/MSAStep.h"
+#include "minecraft/auth/steps/XboxUserStep.h"
+#include "minecraft/auth/steps/XboxAuthorizationStep.h"
+#include "minecraft/auth/steps/LauncherLoginStep.h"
+#include "minecraft/auth/steps/XboxProfileStep.h"
+#include "minecraft/auth/steps/EntitlementsStep.h"
+#include "minecraft/auth/steps/MinecraftProfileStep.h"
+#include "minecraft/auth/steps/GetSkinStep.h"
+
+MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) {
+ m_steps.append(new MSAStep(m_data, MSAStep::Action::Refresh));
+ m_steps.append(new XboxUserStep(m_data));
+ m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
+ m_steps.append(new XboxAuthorizationStep(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
+ m_steps.append(new LauncherLoginStep(m_data));
+ m_steps.append(new XboxProfileStep(m_data));
+ m_steps.append(new EntitlementsStep(m_data));
+ m_steps.append(new MinecraftProfileStep(m_data));
+ m_steps.append(new GetSkinStep(m_data));
+}
+
+MSAInteractive::MSAInteractive(
+ AccountData* data,
+ QObject* parent
+) : AuthFlow(data, parent) {
+ m_steps.append(new MSAStep(m_data, MSAStep::Action::Login));
+ m_steps.append(new XboxUserStep(m_data));
+ m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
+ m_steps.append(new XboxAuthorizationStep(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
+ m_steps.append(new LauncherLoginStep(m_data));
+ m_steps.append(new XboxProfileStep(m_data));
+ m_steps.append(new EntitlementsStep(m_data));
+ m_steps.append(new MinecraftProfileStep(m_data));
+ m_steps.append(new GetSkinStep(m_data));
+}
diff --git a/launcher/minecraft/auth/flows/MSA.h b/launcher/minecraft/auth/flows/MSA.h
new file mode 100644
index 00000000..14a4ff43
--- /dev/null
+++ b/launcher/minecraft/auth/flows/MSA.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "AuthFlow.h"
+
+class MSAInteractive : public AuthFlow
+{
+ Q_OBJECT
+public:
+ explicit MSAInteractive(
+ AccountData *data,
+ QObject *parent = 0
+ );
+};
+
+class MSASilent : public AuthFlow
+{
+ Q_OBJECT
+public:
+ explicit MSASilent(
+ AccountData * data,
+ QObject *parent = 0
+ );
+};
diff --git a/launcher/minecraft/auth/flows/MSAInteractive.cpp b/launcher/minecraft/auth/flows/MSAInteractive.cpp
deleted file mode 100644
index 525aaf88..00000000
--- a/launcher/minecraft/auth/flows/MSAInteractive.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "MSAInteractive.h"
-
-MSAInteractive::MSAInteractive(
- AccountData* data,
- QObject* parent
-) : AuthContext(data, parent) {}
-
-void MSAInteractive::executeTask() {
- m_requestsDone = 0;
- m_xboxProfileSucceeded = false;
- m_mcAuthSucceeded = false;
-
- initMSA();
-
- QVariantMap extraOpts;
- extraOpts["prompt"] = "select_account";
- m_oauth2->setExtraRequestParams(extraOpts);
-
- beginActivity(Katabasis::Activity::LoggingIn);
- *m_data = AccountData();
- m_oauth2->login();
-}
diff --git a/launcher/minecraft/auth/flows/MSAInteractive.h b/launcher/minecraft/auth/flows/MSAInteractive.h
deleted file mode 100644
index 6654e0d6..00000000
--- a/launcher/minecraft/auth/flows/MSAInteractive.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-#include "AuthContext.h"
-
-class MSAInteractive : public AuthContext
-{
- Q_OBJECT
-public:
- explicit MSAInteractive(
- AccountData *data,
- QObject *parent = 0
- );
- void executeTask() override;
-};
diff --git a/launcher/minecraft/auth/flows/MSASilent.cpp b/launcher/minecraft/auth/flows/MSASilent.cpp
deleted file mode 100644
index 8ce43c1f..00000000
--- a/launcher/minecraft/auth/flows/MSASilent.cpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#include "MSASilent.h"
-
-MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthContext(data, parent) {}
-
-void MSASilent::executeTask() {
- m_requestsDone = 0;
- m_xboxProfileSucceeded = false;
- m_mcAuthSucceeded = false;
-
- initMSA();
-
- beginActivity(Katabasis::Activity::Refreshing);
- if(!m_oauth2->refresh()) {
- finishActivity();
- }
-}
diff --git a/launcher/minecraft/auth/flows/MSASilent.h b/launcher/minecraft/auth/flows/MSASilent.h
deleted file mode 100644
index a442b49e..00000000
--- a/launcher/minecraft/auth/flows/MSASilent.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-#include "AuthContext.h"
-
-class MSASilent : public AuthContext
-{
- Q_OBJECT
-public:
- explicit MSASilent(
- AccountData * data,
- QObject *parent = 0
- );
- void executeTask() override;
-};
diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp
new file mode 100644
index 00000000..4661dbe2
--- /dev/null
+++ b/launcher/minecraft/auth/flows/Mojang.cpp
@@ -0,0 +1,27 @@
+#include "Mojang.h"
+
+#include "minecraft/auth/steps/YggdrasilStep.h"
+#include "minecraft/auth/steps/MinecraftProfileStep.h"
+#include "minecraft/auth/steps/MigrationEligibilityStep.h"
+#include "minecraft/auth/steps/GetSkinStep.h"
+
+MojangRefresh::MojangRefresh(
+ AccountData *data,
+ QObject *parent
+) : AuthFlow(data, parent) {
+ m_steps.append(new YggdrasilStep(m_data, QString()));
+ m_steps.append(new MinecraftProfileStep(m_data));
+ m_steps.append(new MigrationEligibilityStep(m_data));
+ m_steps.append(new GetSkinStep(m_data));
+}
+
+MojangLogin::MojangLogin(
+ AccountData *data,
+ QString password,
+ QObject *parent
+): AuthFlow(data, parent), m_password(password) {
+ m_steps.append(new YggdrasilStep(m_data, m_password));
+ m_steps.append(new MinecraftProfileStep(m_data));
+ m_steps.append(new MigrationEligibilityStep(m_data));
+ m_steps.append(new GetSkinStep(m_data));
+}
diff --git a/launcher/minecraft/auth/flows/Mojang.h b/launcher/minecraft/auth/flows/Mojang.h
new file mode 100644
index 00000000..c09c81a8
--- /dev/null
+++ b/launcher/minecraft/auth/flows/Mojang.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "AuthFlow.h"
+
+class MojangRefresh : public AuthFlow
+{
+ Q_OBJECT
+public:
+ explicit MojangRefresh(
+ AccountData *data,
+ QObject *parent = 0
+ );
+};
+
+class MojangLogin : public AuthFlow
+{
+ Q_OBJECT
+public:
+ explicit MojangLogin(
+ AccountData *data,
+ QString password,
+ QObject *parent = 0
+ );
+
+private:
+ QString m_password;
+};
diff --git a/launcher/minecraft/auth/flows/MojangLogin.cpp b/launcher/minecraft/auth/flows/MojangLogin.cpp
deleted file mode 100644
index 6c217cd1..00000000
--- a/launcher/minecraft/auth/flows/MojangLogin.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "MojangLogin.h"
-
-MojangLogin::MojangLogin(
- AccountData *data,
- QString password,
- QObject *parent
-): AuthContext(data, parent), m_password(password) {}
-
-void MojangLogin::executeTask() {
- m_requestsDone = 0;
- m_xboxProfileSucceeded = false;
- m_mcAuthSucceeded = false;
-
- initMojang();
-
- beginActivity(Katabasis::Activity::LoggingIn);
- m_yggdrasil->login(m_password);
-}
diff --git a/launcher/minecraft/auth/flows/MojangLogin.h b/launcher/minecraft/auth/flows/MojangLogin.h
deleted file mode 100644
index 5f33752f..00000000
--- a/launcher/minecraft/auth/flows/MojangLogin.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-#include "AuthContext.h"
-
-class MojangLogin : public AuthContext
-{
- Q_OBJECT
-public:
- explicit MojangLogin(
- AccountData *data,
- QString password,
- QObject *parent = 0
- );
- void executeTask() override;
-
-private:
- QString m_password;
-};
diff --git a/launcher/minecraft/auth/flows/MojangRefresh.cpp b/launcher/minecraft/auth/flows/MojangRefresh.cpp
deleted file mode 100644
index 008c0453..00000000
--- a/launcher/minecraft/auth/flows/MojangRefresh.cpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#include "MojangRefresh.h"
-
-MojangRefresh::MojangRefresh(
- AccountData *data,
- QObject *parent
-) : AuthContext(data, parent) {}
-
-void MojangRefresh::executeTask() {
- m_requestsDone = 0;
- m_xboxProfileSucceeded = false;
- m_mcAuthSucceeded = false;
-
- initMojang();
-
- beginActivity(Katabasis::Activity::Refreshing);
- m_yggdrasil->refresh();
-}
diff --git a/launcher/minecraft/auth/flows/MojangRefresh.h b/launcher/minecraft/auth/flows/MojangRefresh.h
deleted file mode 100644
index 06e4e4ce..00000000
--- a/launcher/minecraft/auth/flows/MojangRefresh.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-#include "AuthContext.h"
-
-class MojangRefresh : public AuthContext
-{
- Q_OBJECT
-public:
- 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
deleted file mode 100644
index ecb11cf9..00000000
--- a/launcher/minecraft/auth/flows/Parsers.cpp
+++ /dev/null
@@ -1,316 +0,0 @@
-#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
deleted file mode 100644
index b484a073..00000000
--- a/launcher/minecraft/auth/flows/Parsers.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#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
deleted file mode 100644
index 5ea168e8..00000000
--- a/launcher/minecraft/auth/flows/Yggdrasil.cpp
+++ /dev/null
@@ -1,331 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Yggdrasil.h"
-#include "../AccountData.h"
-
-#include <QObject>
-#include <QString>
-#include <QJsonObject>
-#include <QJsonDocument>
-#include <QNetworkReply>
-#include <QByteArray>
-
-#include <QDebug>
-
-#include "Application.h"
-
-Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
- : AccountTask(data, parent)
-{
- changeState(STATE_CREATED);
-}
-
-void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
- changeState(STATE_WORKING);
-
- QNetworkRequest netRequest(endpoint);
- netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- 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);
- connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors);
- timeout_keeper.setSingleShot(true);
- timeout_keeper.start(timeout_max);
- counter.setSingleShot(false);
- counter.start(time_step);
- progress(0, timeout_max);
- connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout);
- connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
-}
-
-void Yggdrasil::executeTask() {
-}
-
-void Yggdrasil::refresh() {
- start();
- /*
- * {
- * "clientToken": "client identifier"
- * "accessToken": "current access token to be refreshed"
- * "selectedProfile": // specifying this causes errors
- * {
- * "id": "profile ID"
- * "name": "profile name"
- * }
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
- req.insert("clientToken", m_data->clientToken());
- req.insert("accessToken", m_data->accessToken());
- /*
- {
- auto currentProfile = m_account->currentProfile();
- QJsonObject profile;
- profile.insert("id", currentProfile->id());
- profile.insert("name", currentProfile->name());
- req.insert("selectedProfile", profile);
- }
- */
- req.insert("requestUser", false);
- QJsonDocument doc(req);
-
- QUrl reqUrl("https://authserver.mojang.com/refresh");
- QByteArray requestData = doc.toJson();
-
- sendRequest(reqUrl, requestData);
-}
-
-void Yggdrasil::login(QString password) {
- start();
- /*
- * {
- * "agent": { // optional
- * "name": "Minecraft", // So far this is the only encountered value
- * "version": 1 // This number might be increased
- * // by the vanilla client in the future
- * },
- * "username": "mojang account name", // Can be an email address or player name for
- * // unmigrated accounts
- * "password": "mojang account password",
- * "clientToken": "client identifier", // optional
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
-
- {
- QJsonObject agent;
- // C++ makes string literals void* for some stupid reason, so we have to tell it
- // QString... Thanks Obama.
- agent.insert("name", QString("Minecraft"));
- agent.insert("version", 1);
- req.insert("agent", agent);
- }
-
- req.insert("username", m_data->userName());
- req.insert("password", password);
- req.insert("requestUser", false);
-
- // If we already have a client token, give it to the server.
- // Otherwise, let the server give us one.
-
- m_data->generateClientTokenIfMissing();
- req.insert("clientToken", m_data->clientToken());
-
- QJsonDocument doc(req);
-
- QUrl reqUrl("https://authserver.mojang.com/authenticate");
- QNetworkRequest netRequest(reqUrl);
- QByteArray requestData = doc.toJson();
-
- sendRequest(reqUrl, requestData);
-}
-
-
-
-void Yggdrasil::refreshTimers(qint64, qint64) {
- timeout_keeper.stop();
- timeout_keeper.start(timeout_max);
- progress(count = 0, timeout_max);
-}
-
-void Yggdrasil::heartbeat() {
- count += time_step;
- progress(count, timeout_max);
-}
-
-bool Yggdrasil::abort() {
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = Yggdrasil::BY_USER;
- m_netReply->abort();
- return true;
-}
-
-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) {
- 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 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.";
-
- // qDebug() << 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()) {
- // Fail if the server gave us an empty client token
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
- return;
- }
- if(m_data->clientToken().isEmpty()) {
- m_data->setClientToken(clientToken);
- }
- else if(clientToken != m_data->clientToken()) {
- changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
- return;
- }
-
- // Now, we set the access token.
- qDebug() << "Getting access token.";
- QString accessToken = responseData.value("accessToken").toString("");
- 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;
- }
- // 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.
- qDebug() << "Finished reading authentication response.";
- changeState(STATE_SUCCEEDED);
-}
-
-void Yggdrasil::processReply() {
- changeState(STATE_WORKING);
-
- switch (m_netReply->error())
- {
- case QNetworkReply::NoError:
- break;
- case QNetworkReply::TimeoutError:
- changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
- return;
- case QNetworkReply::OperationCanceledError:
- changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
- return;
- case QNetworkReply::SslHandshakeFailedError:
- changeState(
- STATE_FAILED_SOFT,
- tr(
- "<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
- "<ul>"
- "<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 log file for details</li>"
- "</ul>"
- )
- );
- return;
- // used for invalid credentials and similar errors. Fall through.
- case QNetworkReply::ContentAccessDenied:
- case QNetworkReply::ContentOperationNotPermittedError:
- break;
- case QNetworkReply::ContentGoneError: {
- changeState(
- STATE_FAILED_GONE,
- tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
- );
- }
- default:
- changeState(
- STATE_FAILED_SOFT,
- tr("Authentication operation failed due to a network error: %1 (%2)").arg(m_netReply->errorString()).arg(m_netReply->error())
- );
- return;
- }
-
- // Try to parse the response regardless of the response code.
- // Sometimes the auth server will give more information and an error code.
- QJsonParseError jsonError;
- QByteArray replyData = m_netReply->readAll();
- QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
- // Check the response code.
- int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- 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) {
- processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
- return;
- }
- else {
- changeState(
- STATE_FAILED_SOFT,
- tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)
- );
- qCritical() << replyData;
- }
- return;
- }
-
- // If the response code was not 200, then Yggdrasil may have given us information
- // 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) {
- // 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 {
- // 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.";
- changeState(
- STATE_FAILED_SOFT,
- tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString())
- );
- }
-}
-
-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()) {
- m_error = std::shared_ptr<Error>(
- new Error {
- errorVal.toString(""),
- errorMessageValue.toString(""),
- causeVal.toString("")
- }
- );
- changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
- }
- 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
deleted file mode 100644
index b9670ec7..00000000
--- a/launcher/minecraft/auth/flows/Yggdrasil.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "../AccountTask.h"
-
-#include <QString>
-#include <QJsonObject>
-#include <QTimer>
-#include <qsslerror.h>
-
-#include "../MinecraftAccount.h"
-
-class QNetworkAccessManager;
-class QNetworkReply;
-
-/**
- * A Yggdrasil task is a task that performs an operation on a given mojang account.
- */
-class Yggdrasil : public AccountTask
-{
- Q_OBJECT
-public:
- explicit Yggdrasil(
- AccountData *data,
- QObject *parent = 0
- );
- virtual ~Yggdrasil() {};
-
- void refresh();
- void login(QString password);
-protected:
- void executeTask() override;
-
- /**
- * Processes the response received from the server.
- * If an error occurred, this should emit a failed signal.
- * If Yggdrasil gave an error response, it should call setError() first, and then return false.
- * Otherwise, it should return true.
- * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
- * an empty QJsonObject.
- */
- void processResponse(QJsonObject responseData);
-
- /**
- * Processes an error response received from the server.
- * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
- * \returns a QString error message that will be passed to emitFailed.
- */
- virtual void processError(QJsonObject responseData);
-
-protected slots:
- void processReply();
- void refreshTimers(qint64, qint64);
- void heartbeat();
- void sslErrors(QList<QSslError>);
- void abortByTimeout();
-
-public slots:
- virtual bool abort() override;
-
-private:
- void sendRequest(QUrl endpoint, QByteArray content);
-
-protected:
- QNetworkReply *m_netReply = nullptr;
- QTimer timeout_keeper;
- QTimer counter;
- int count = 0; // num msec since time reset
-
- const int timeout_max = 30000;
- const int time_step = 50;
-};