mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-10-03 16:51:30 +00:00
GH-4317 Detect forced migration state and show errors for it
This commit is contained in:
@@ -224,6 +224,8 @@ set(MINECRAFT_SOURCES
|
|||||||
|
|
||||||
minecraft/auth/steps/EntitlementsStep.cpp
|
minecraft/auth/steps/EntitlementsStep.cpp
|
||||||
minecraft/auth/steps/EntitlementsStep.h
|
minecraft/auth/steps/EntitlementsStep.h
|
||||||
|
minecraft/auth/steps/ForcedMigrationStep.cpp
|
||||||
|
minecraft/auth/steps/ForcedMigrationStep.h
|
||||||
minecraft/auth/steps/GetSkinStep.cpp
|
minecraft/auth/steps/GetSkinStep.cpp
|
||||||
minecraft/auth/steps/GetSkinStep.h
|
minecraft/auth/steps/GetSkinStep.h
|
||||||
minecraft/auth/steps/LauncherLoginStep.cpp
|
minecraft/auth/steps/LauncherLoginStep.cpp
|
||||||
|
@@ -239,6 +239,18 @@ void LaunchController::login() {
|
|||||||
emitFailed(errorString);
|
emitFailed(errorString);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case AccountState::MustMigrate: {
|
||||||
|
auto errorString = tr("The account must be migrated to a Microsoft account.");
|
||||||
|
QMessageBox::warning(
|
||||||
|
m_parentWidget,
|
||||||
|
tr("Account requires migration"),
|
||||||
|
errorString,
|
||||||
|
QMessageBox::StandardButton::Ok,
|
||||||
|
QMessageBox::StandardButton::Ok
|
||||||
|
);
|
||||||
|
emitFailed(errorString);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emitFailed(tr("Failed to launch."));
|
emitFailed(tr("Failed to launch."));
|
||||||
|
@@ -322,6 +322,7 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
|||||||
if(type == AccountType::Mojang) {
|
if(type == AccountType::Mojang) {
|
||||||
legacy = data.value("legacy").toBool(false);
|
legacy = data.value("legacy").toBool(false);
|
||||||
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
|
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
|
||||||
|
mustMigrateToMSA = data.value("mustMigrateToMSA").toBool(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type == AccountType::MSA) {
|
if(type == AccountType::MSA) {
|
||||||
@@ -355,6 +356,9 @@ QJsonObject AccountData::saveState() const {
|
|||||||
if(canMigrateToMSA) {
|
if(canMigrateToMSA) {
|
||||||
output["canMigrateToMSA"] = true;
|
output["canMigrateToMSA"] = true;
|
||||||
}
|
}
|
||||||
|
if(mustMigrateToMSA) {
|
||||||
|
output["mustMigrateToMSA"] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (type == AccountType::MSA) {
|
else if (type == AccountType::MSA) {
|
||||||
output["type"] = "MSA";
|
output["type"] = "MSA";
|
||||||
|
@@ -48,7 +48,8 @@ enum class AccountState {
|
|||||||
Online,
|
Online,
|
||||||
Errored,
|
Errored,
|
||||||
Expired,
|
Expired,
|
||||||
Gone
|
Gone,
|
||||||
|
MustMigrate
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AccountData {
|
struct AccountData {
|
||||||
@@ -79,6 +80,7 @@ struct AccountData {
|
|||||||
AccountType type = AccountType::MSA;
|
AccountType type = AccountType::MSA;
|
||||||
bool legacy = false;
|
bool legacy = false;
|
||||||
bool canMigrateToMSA = false;
|
bool canMigrateToMSA = false;
|
||||||
|
bool mustMigrateToMSA = false;
|
||||||
|
|
||||||
Katabasis::Token msaToken;
|
Katabasis::Token msaToken;
|
||||||
Katabasis::Token userToken;
|
Katabasis::Token userToken;
|
||||||
|
@@ -294,6 +294,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
|||||||
case AccountState::Gone: {
|
case AccountState::Gone: {
|
||||||
return tr("Gone", "Account status");
|
return tr("Gone", "Account status");
|
||||||
}
|
}
|
||||||
|
case AccountState::MustMigrate: {
|
||||||
|
return tr("Must Migrate", "Account status");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -45,6 +45,8 @@ QString AccountTask::getStateMessage() const
|
|||||||
return tr("Failed to contact the authentication server.");
|
return tr("Failed to contact the authentication server.");
|
||||||
case AccountTaskState::STATE_FAILED_SOFT:
|
case AccountTaskState::STATE_FAILED_SOFT:
|
||||||
return tr("Encountered an error during authentication.");
|
return tr("Encountered an error during authentication.");
|
||||||
|
case AccountTaskState::STATE_FAILED_MUST_MIGRATE:
|
||||||
|
return tr("Failed to authenticate. The account must be migrated to a Microsoft account to be usable.");
|
||||||
case AccountTaskState::STATE_FAILED_HARD:
|
case AccountTaskState::STATE_FAILED_HARD:
|
||||||
return tr("Failed to authenticate. The session has expired.");
|
return tr("Failed to authenticate. The session has expired.");
|
||||||
case AccountTaskState::STATE_FAILED_GONE:
|
case AccountTaskState::STATE_FAILED_GONE:
|
||||||
@@ -84,6 +86,12 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
|
|||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_MUST_MIGRATE: {
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::MustMigrate;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
case AccountTaskState::STATE_FAILED_HARD: {
|
case AccountTaskState::STATE_FAILED_HARD: {
|
||||||
m_data->errorString = reason;
|
m_data->errorString = reason;
|
||||||
m_data->accountState = AccountState::Expired;
|
m_data->accountState = AccountState::Expired;
|
||||||
|
@@ -36,6 +36,7 @@ enum class AccountTaskState
|
|||||||
STATE_WORKING,
|
STATE_WORKING,
|
||||||
STATE_SUCCEEDED,
|
STATE_SUCCEEDED,
|
||||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
||||||
|
STATE_FAILED_MUST_MIGRATE, //!< soft failure. main tokens are valid, but the account must be migrated
|
||||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
||||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
||||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
||||||
|
@@ -145,6 +145,7 @@ void MinecraftAccount::authFailed(QString reason)
|
|||||||
{
|
{
|
||||||
switch (m_currentTask->taskState()) {
|
switch (m_currentTask->taskState()) {
|
||||||
case AccountTaskState::STATE_OFFLINE:
|
case AccountTaskState::STATE_OFFLINE:
|
||||||
|
case AccountTaskState::STATE_FAILED_MUST_MIGRATE:
|
||||||
case AccountTaskState::STATE_FAILED_SOFT: {
|
case AccountTaskState::STATE_FAILED_SOFT: {
|
||||||
// NOTE: this doesn't do much. There was an error of some sort.
|
// NOTE: this doesn't do much. There was an error of some sort.
|
||||||
}
|
}
|
||||||
|
@@ -247,6 +247,36 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool parseForcedMigrationResponse(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/msamigrationforced 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 != "msamigrationforced") {
|
||||||
|
qWarning() << "Rollout feature is not what we expected (msamigrationforced), but is instead \"" << feature << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!getBool(obj.value("rollout"), result)) {
|
||||||
|
qWarning() << "Rollout feature is not a string";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool parseRolloutResponse(QByteArray & data, bool& result) {
|
bool parseRolloutResponse(QByteArray & data, bool& result) {
|
||||||
qDebug() << "Parsing Rollout response...";
|
qDebug() << "Parsing Rollout response...";
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
@@ -16,4 +16,5 @@ namespace Parsers
|
|||||||
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
|
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
|
||||||
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
|
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
|
||||||
bool parseRolloutResponse(QByteArray &data, bool& result);
|
bool parseRolloutResponse(QByteArray &data, bool& result);
|
||||||
|
bool parseForcedMigrationResponse(QByteArray & data, bool& result);
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||||
|
#include "minecraft/auth/steps/ForcedMigrationStep.h"
|
||||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ MojangRefresh::MojangRefresh(
|
|||||||
QObject *parent
|
QObject *parent
|
||||||
) : AuthFlow(data, parent) {
|
) : AuthFlow(data, parent) {
|
||||||
m_steps.append(new YggdrasilStep(m_data, QString()));
|
m_steps.append(new YggdrasilStep(m_data, QString()));
|
||||||
|
m_steps.append(new ForcedMigrationStep(m_data));
|
||||||
m_steps.append(new MinecraftProfileStep(m_data));
|
m_steps.append(new MinecraftProfileStep(m_data));
|
||||||
m_steps.append(new MigrationEligibilityStep(m_data));
|
m_steps.append(new MigrationEligibilityStep(m_data));
|
||||||
m_steps.append(new GetSkinStep(m_data));
|
m_steps.append(new GetSkinStep(m_data));
|
||||||
@@ -21,6 +23,7 @@ MojangLogin::MojangLogin(
|
|||||||
QObject *parent
|
QObject *parent
|
||||||
): AuthFlow(data, parent), m_password(password) {
|
): AuthFlow(data, parent), m_password(password) {
|
||||||
m_steps.append(new YggdrasilStep(m_data, m_password));
|
m_steps.append(new YggdrasilStep(m_data, m_password));
|
||||||
|
m_steps.append(new ForcedMigrationStep(m_data));
|
||||||
m_steps.append(new MinecraftProfileStep(m_data));
|
m_steps.append(new MinecraftProfileStep(m_data));
|
||||||
m_steps.append(new MigrationEligibilityStep(m_data));
|
m_steps.append(new MigrationEligibilityStep(m_data));
|
||||||
m_steps.append(new GetSkinStep(m_data));
|
m_steps.append(new GetSkinStep(m_data));
|
||||||
|
52
launcher/minecraft/auth/steps/ForcedMigrationStep.cpp
Normal file
52
launcher/minecraft/auth/steps/ForcedMigrationStep.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include "ForcedMigrationStep.h"
|
||||||
|
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
|
||||||
|
ForcedMigrationStep::ForcedMigrationStep(AccountData* data) : AuthStep(data) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ForcedMigrationStep::~ForcedMigrationStep() noexcept = default;
|
||||||
|
|
||||||
|
QString ForcedMigrationStep::describe() {
|
||||||
|
return tr("Checking for migration eligibility.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForcedMigrationStep::perform() {
|
||||||
|
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigrationforced");
|
||||||
|
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, &ForcedMigrationStep::onRequestDone);
|
||||||
|
requestor->get(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForcedMigrationStep::rehydrate() {
|
||||||
|
// NOOP, for now. We only save bools and there's nothing to check.
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForcedMigrationStep::onRequestDone(
|
||||||
|
QNetworkReply::NetworkError error,
|
||||||
|
QByteArray data,
|
||||||
|
QList<QNetworkReply::RawHeaderPair> headers
|
||||||
|
) {
|
||||||
|
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||||
|
requestor->deleteLater();
|
||||||
|
|
||||||
|
if (error == QNetworkReply::NoError) {
|
||||||
|
Parsers::parseForcedMigrationResponse(data, m_data->mustMigrateToMSA);
|
||||||
|
}
|
||||||
|
if(m_data->mustMigrateToMSA) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_MUST_MIGRATE, tr("The account must be migrated to a Microsoft account."));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got forced migration flags"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
23
launcher/minecraft/auth/steps/ForcedMigrationStep.h
Normal file
23
launcher/minecraft/auth/steps/ForcedMigrationStep.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "QObjectPtr.h"
|
||||||
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
|
||||||
|
|
||||||
|
class ForcedMigrationStep : public AuthStep {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ForcedMigrationStep(AccountData *data);
|
||||||
|
virtual ~ForcedMigrationStep() noexcept;
|
||||||
|
|
||||||
|
void perform() override;
|
||||||
|
void rehydrate() override;
|
||||||
|
|
||||||
|
QString describe() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||||
|
};
|
||||||
|
|
Reference in New Issue
Block a user