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.
This commit is contained in:
@ -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: {
|
||||
|
@ -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")) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
doUserAuth();
|
||||
}
|
||||
|
||||
void AuthContext::onOAuthActivityChanged(Katabasis::Activity activity) {
|
||||
// respond to activity change here
|
||||
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() {
|
||||
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include <QDrag>
|
||||
#include <QPainter>
|
||||
#include "VersionListView.h"
|
||||
#include "Common.h"
|
||||
|
||||
VersionListView::VersionListView(QWidget *parent)
|
||||
:QTreeView ( parent )
|
||||
|
Reference in New Issue
Block a user