diff options
author | Petr Mrázek <peterix@gmail.com> | 2021-11-28 18:42:01 +0100 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2021-11-28 18:42:01 +0100 |
commit | 285188ea532d0e4d1b2b798f1ab13e35202dca0d (patch) | |
tree | 60881ae834cb96eee61ab78e9845b58c8c6066db /launcher/minecraft/auth | |
parent | 0e31f77468a8bf391ef2b31124f7882b7d907881 (diff) | |
download | PrismLauncher-285188ea532d0e4d1b2b798f1ab13e35202dca0d.tar.gz PrismLauncher-285188ea532d0e4d1b2b798f1ab13e35202dca0d.tar.bz2 PrismLauncher-285188ea532d0e4d1b2b798f1ab13e35202dca0d.zip |
GH-4071 handle network errors when logging in with MSA as 'soft'
This makes the tokens not expire when such errors happen.
Only applies to MSA, not the XBox and Mojang steps afterwards.
Further testing and improvements are still needed.
Diffstat (limited to 'launcher/minecraft/auth')
-rw-r--r-- | launcher/minecraft/auth/AccountList.cpp | 9 | ||||
-rw-r--r-- | launcher/minecraft/auth/MinecraftAccount.cpp | 24 | ||||
-rw-r--r-- | launcher/minecraft/auth/MinecraftAccount.h | 9 | ||||
-rw-r--r-- | launcher/minecraft/auth/flows/AuthContext.cpp | 109 | ||||
-rw-r--r-- | launcher/minecraft/auth/flows/AuthContext.h | 7 | ||||
-rw-r--r-- | launcher/minecraft/auth/flows/MSAInteractive.cpp | 3 |
6 files changed, 100 insertions, 61 deletions
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 97ba48f4..d7537345 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -244,8 +244,13 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } case StatusColumn: { - auto isActive = account->isActive(); - return isActive ? "Working" : "Ready"; + if(account->isActive()) { + return tr("Working", "Account status"); + } + if(account->isExpired()) { + return tr("Expired", "Account status"); + } + return tr("Ready", "Account status"); } case ProfileNameColumn: { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 5cfec49d..30ed6afe 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -34,6 +34,11 @@ #include "flows/MojangRefresh.h" #include "flows/MojangLogin.h" +MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { + m_internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); +} + + MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json) { MinecraftAccountPtr account(new MinecraftAccount()); if(account->data.resumeStateFromV2(json)) { @@ -52,7 +57,7 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) { MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username) { - MinecraftAccountPtr account(new MinecraftAccount()); + MinecraftAccountPtr account = new MinecraftAccount(); account->data.type = AccountType::Mojang; account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); @@ -91,6 +96,23 @@ AccountStatus MinecraftAccount::accountStatus() const { } } +bool MinecraftAccount::isExpired() const { + switch(data.type) { + case AccountType::Mojang: { + return data.accessToken().isEmpty(); + } + break; + case AccountType::MSA: { + return data.msaToken.validity == Katabasis::Validity::None; + } + break; + default: { + return true; + } + } +} + + QPixmap MinecraftAccount::getFace() const { QPixmap skinTexture; if(!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) { diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 928d3742..459ef903 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -72,7 +72,7 @@ public: /* construction */ explicit MinecraftAccount(const MinecraftAccount &other, QObject *parent) = delete; //! Default constructor - explicit MinecraftAccount(QObject *parent = 0) : QObject(parent) {}; + explicit MinecraftAccount(QObject *parent = 0); static MinecraftAccountPtr createFromUsername(const QString &username); @@ -97,6 +97,10 @@ public: /* manipulation */ shared_qobject_ptr<AccountTask> refresh(AuthSessionPtr session); public: /* queries */ + QString internalId() const { + return m_internalId; + } + QString accountDisplayString() const { return data.accountDisplayString(); } @@ -119,6 +123,8 @@ public: /* queries */ bool isActive() const; + bool isExpired() const; + bool canMigrate() const { return data.canMigrateToMSA; } @@ -168,6 +174,7 @@ signals: // TODO: better signalling for the various possible state changes - especially errors protected: /* variables */ + QString m_internalId; AccountData data; // current task we are executing here diff --git a/launcher/minecraft/auth/flows/AuthContext.cpp b/launcher/minecraft/auth/flows/AuthContext.cpp index 34e2bea8..00957fd4 100644 --- a/launcher/minecraft/auth/flows/AuthContext.cpp +++ b/launcher/minecraft/auth/flows/AuthContext.cpp @@ -18,7 +18,7 @@ #include <Application.h> -using OAuth2 = Katabasis::OAuth2; +using OAuth2 = Katabasis::DeviceFlow; using Activity = Katabasis::Activity; AuthContext::AuthContext(AccountData * data, QObject *parent) : @@ -50,21 +50,17 @@ void AuthContext::initMSA() { return; } - Katabasis::OAuth2::Options opts; + 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"; - opts.listenerPorts = {28562, 28563, 28564, 28565, 28566}; // FIXME: OAuth2 is not aware of our fancy shared pointers m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get()); - m_oauth2->setGrantFlow(Katabasis::OAuth2::GrantFlowDevice); - 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() { @@ -78,7 +74,7 @@ void AuthContext::initMojang() { } void AuthContext::onMojangSucceeded() { - doEntitlements(); + doMinecraftProfile(); } @@ -89,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() { @@ -226,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(); @@ -543,6 +545,10 @@ void AuthContext::onMinecraftProfileDone( #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; @@ -560,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 { diff --git a/launcher/minecraft/auth/flows/AuthContext.h b/launcher/minecraft/auth/flows/AuthContext.h index dcb91613..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 @@ -87,7 +84,7 @@ protected: void clearTokens(); protected: - Katabasis::OAuth2 *m_oauth2 = nullptr; + Katabasis::DeviceFlow *m_oauth2 = nullptr; Yggdrasil *m_yggdrasil = nullptr; int m_requestsDone = 0; diff --git a/launcher/minecraft/auth/flows/MSAInteractive.cpp b/launcher/minecraft/auth/flows/MSAInteractive.cpp index 6c597cf7..525aaf88 100644 --- a/launcher/minecraft/auth/flows/MSAInteractive.cpp +++ b/launcher/minecraft/auth/flows/MSAInteractive.cpp @@ -17,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(); } |