Improve login UI

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-05-15 17:25:15 +03:00
parent 849c3faeb4
commit abedc6a23c
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
25 changed files with 159 additions and 287 deletions

View File

@ -217,15 +217,9 @@ set(MINECRAFT_SOURCES
minecraft/auth/Parsers.cpp minecraft/auth/Parsers.cpp
minecraft/auth/Parsers.h minecraft/auth/Parsers.h
minecraft/auth/flows/AuthFlow.cpp minecraft/auth/AuthFlow.cpp
minecraft/auth/flows/AuthFlow.h minecraft/auth/AuthFlow.h
minecraft/auth/flows/MSA.cpp
minecraft/auth/flows/MSA.h
minecraft/auth/flows/Offline.cpp
minecraft/auth/flows/Offline.h
minecraft/auth/steps/OfflineStep.cpp
minecraft/auth/steps/OfflineStep.h
minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.cpp
minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/EntitlementsStep.h
minecraft/auth/steps/GetSkinStep.cpp minecraft/auth/steps/GetSkinStep.cpp

View File

@ -42,7 +42,7 @@
#include <QUuid> #include <QUuid>
namespace { namespace {
void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName) void tokenToJSONV3(QJsonObject& parent, Token t, const char* tokenName)
{ {
if (!t.persistent) { if (!t.persistent) {
return; return;
@ -74,9 +74,9 @@ void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenNam
} }
} }
Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName) Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
{ {
Katabasis::Token out; Token out;
auto tokenObject = parent.value(tokenName).toObject(); auto tokenObject = parent.value(tokenName).toObject();
if (tokenObject.isEmpty()) { if (tokenObject.isEmpty()) {
return out; return out;
@ -94,7 +94,7 @@ Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenNam
auto token = tokenObject.value("token"); auto token = tokenObject.value("token");
if (token.isString()) { if (token.isString()) {
out.token = token.toString(); out.token = token.toString();
out.validity = Katabasis::Validity::Assumed; out.validity = Validity::Assumed;
} }
auto refresh_token = tokenObject.value("refresh_token"); auto refresh_token = tokenObject.value("refresh_token");
@ -241,13 +241,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN
} }
} }
} }
out.validity = Katabasis::Validity::Assumed; out.validity = Validity::Assumed;
return out; return out;
} }
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p) void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
{ {
if (p.validity == Katabasis::Validity::None) { if (p.validity == Validity::None) {
return; return;
} }
QJsonObject out; QJsonObject out;
@ -271,7 +271,7 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
} }
out.canPlayMinecraft = canPlayMinecraftV.toBool(false); out.canPlayMinecraft = canPlayMinecraftV.toBool(false);
out.ownsMinecraft = ownsMinecraftV.toBool(false); out.ownsMinecraft = ownsMinecraftV.toBool(false);
out.validity = Katabasis::Validity::Assumed; out.validity = Validity::Assumed;
} }
return true; return true;
} }
@ -313,10 +313,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
minecraftProfile = profileFromJSONV3(data, "profile"); minecraftProfile = profileFromJSONV3(data, "profile");
if (!entitlementFromJSONV3(data, minecraftEntitlement)) { if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
if (minecraftProfile.validity != Katabasis::Validity::None) { if (minecraftProfile.validity != Validity::None) {
minecraftEntitlement.canPlayMinecraft = true; minecraftEntitlement.canPlayMinecraft = true;
minecraftEntitlement.ownsMinecraft = true; minecraftEntitlement.ownsMinecraft = true;
minecraftEntitlement.validity = Katabasis::Validity::Assumed; minecraftEntitlement.validity = Validity::Assumed;
} }
} }

View File

@ -44,18 +44,6 @@
#include <QString> #include <QString>
#include <QVariantMap> #include <QVariantMap>
namespace Katabasis {
enum class Activity {
Idle,
LoggingIn,
LoggingOut,
Refreshing,
FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated
FailedHard, //!< hard failure. auth is invalid
FailedGone, //!< hard failure. auth is invalid, and the account no longer exists
Succeeded
};
enum class Validity { None, Assumed, Certain }; enum class Validity { None, Assumed, Certain };
struct Token { struct Token {
@ -69,8 +57,6 @@ struct Token {
bool persistent = true; bool persistent = true;
}; };
} // namespace Katabasis
struct Skin { struct Skin {
QString id; QString id;
QString url; QString url;
@ -90,7 +76,7 @@ struct Cape {
struct MinecraftEntitlement { struct MinecraftEntitlement {
bool ownsMinecraft = false; bool ownsMinecraft = false;
bool canPlayMinecraft = false; bool canPlayMinecraft = false;
Katabasis::Validity validity = Katabasis::Validity::None; Validity validity = Validity::None;
}; };
struct MinecraftProfile { struct MinecraftProfile {
@ -99,7 +85,7 @@ struct MinecraftProfile {
Skin skin; Skin skin;
QString currentCape; QString currentCape;
QMap<QString, Cape> capes; QMap<QString, Cape> capes;
Katabasis::Validity validity = Katabasis::Validity::None; Validity validity = Validity::None;
}; };
enum class AccountType { MSA, Offline }; enum class AccountType { MSA, Offline };
@ -124,15 +110,15 @@ struct AccountData {
AccountType type = AccountType::MSA; AccountType type = AccountType::MSA;
QString msaClientID; QString msaClientID;
Katabasis::Token msaToken; Token msaToken;
Katabasis::Token userToken; Token userToken;
Katabasis::Token xboxApiToken; Token xboxApiToken;
Katabasis::Token mojangservicesToken; Token mojangservicesToken;
Katabasis::Token yggdrasilToken; Token yggdrasilToken;
MinecraftProfile minecraftProfile; MinecraftProfile minecraftProfile;
MinecraftEntitlement minecraftEntitlement; MinecraftEntitlement minecraftEntitlement;
Katabasis::Validity validity_ = Katabasis::Validity::None; Validity validity_ = Validity::None;
// runtime only information (not saved with the account) // runtime only information (not saved with the account)
QString internalId; QString internalId;

View File

@ -36,7 +36,7 @@
#pragma once #pragma once
#include "MinecraftAccount.h" #include "MinecraftAccount.h"
#include "minecraft/auth/flows/AuthFlow.h" #include "minecraft/auth/AuthFlow.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QObject> #include <QObject>

View File

@ -3,27 +3,47 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/steps/EntitlementsStep.h"
#include "minecraft/auth/steps/GetSkinStep.h"
#include "minecraft/auth/steps/LauncherLoginStep.h"
#include "minecraft/auth/steps/MSAStep.h"
#include "minecraft/auth/steps/MinecraftProfileStep.h"
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
#include "minecraft/auth/steps/XboxProfileStep.h"
#include "minecraft/auth/steps/XboxUserStep.h"
#include "AuthFlow.h" #include "AuthFlow.h"
#include <Application.h> #include <Application.h>
AuthFlow::AuthFlow(AccountData* data, QObject* parent) : Task(parent), m_data(data) AuthFlow::AuthFlow(AccountData* data, bool silent, QObject* parent) : Task(parent), m_data(data)
{ {
if (data->type == AccountType::MSA) {
auto oauthStep = makeShared<MSAStep>(m_data, silent);
connect(oauthStep.get(), &MSAStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser);
m_steps.append(oauthStep);
m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
m_steps.append(
makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
m_steps.append(makeShared<LauncherLoginStep>(m_data));
m_steps.append(makeShared<XboxProfileStep>(m_data));
m_steps.append(makeShared<EntitlementsStep>(m_data));
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
m_steps.append(makeShared<GetSkinStep>(m_data));
}
changeState(AccountTaskState::STATE_CREATED); changeState(AccountTaskState::STATE_CREATED);
} }
void AuthFlow::succeed() void AuthFlow::succeed()
{ {
m_data->validity_ = Katabasis::Validity::Certain; m_data->validity_ = Validity::Certain;
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps")); changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
} }
void AuthFlow::executeTask() void AuthFlow::executeTask()
{ {
if (m_currentStep) {
emitFailed("No task");
return;
}
changeState(AccountTaskState::STATE_WORKING, tr("Initializing")); changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
nextStep(); nextStep();
} }
@ -46,9 +66,8 @@ void AuthFlow::nextStep()
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
{ {
if (changeState(resultingState, message)) { if (changeState(resultingState, message))
nextStep(); nextStep();
}
} }
bool AuthFlow::changeState(AccountTaskState newState, QString reason) bool AuthFlow::changeState(AccountTaskState newState, QString reason)

View File

@ -15,30 +15,26 @@ class AuthFlow : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit AuthFlow(AccountData* data, QObject* parent = 0); explicit AuthFlow(AccountData* data, bool silent = false, QObject* parent = 0);
virtual ~AuthFlow() = default; virtual ~AuthFlow() = default;
Katabasis::Validity validity() { return m_data->validity_; };
void executeTask() override; void executeTask() override;
AccountTaskState taskState() { return m_taskState; } AccountTaskState taskState() { return m_taskState; }
signals: signals:
void activityChanged(Katabasis::Activity activity); void authorizeWithBrowser(const QUrl& url);
protected: protected:
void succeed(); void succeed();
void nextStep(); void nextStep();
protected slots: private slots:
// NOTE: true -> non-terminal state, false -> terminal state // NOTE: true -> non-terminal state, false -> terminal state
bool changeState(AccountTaskState newState, QString reason = QString()); bool changeState(AccountTaskState newState, QString reason = QString());
private slots:
void stepFinished(AccountTaskState resultingState, QString message); void stepFinished(AccountTaskState resultingState, QString message);
protected: private:
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED; AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
QList<AuthStep::Ptr> m_steps; QList<AuthStep::Ptr> m_steps;
AuthStep::Ptr m_currentStep; AuthStep::Ptr m_currentStep;

View File

@ -50,11 +50,8 @@
#include <QPainter> #include <QPainter>
#include "flows/MSA.h"
#include "flows/Offline.h"
#include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountData.h"
#include "minecraft/auth/flows/AuthFlow.h" #include "minecraft/auth/AuthFlow.h"
#include "tasks/Task.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
{ {
@ -82,7 +79,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
auto account = makeShared<MinecraftAccount>(); auto account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Offline; account->data.type = AccountType::Offline;
account->data.yggdrasilToken.token = "0"; account->data.yggdrasilToken.token = "0";
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; account->data.yggdrasilToken.validity = Validity::Certain;
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["userName"] = username;
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
@ -90,7 +87,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
account->data.minecraftEntitlement.canPlayMinecraft = true; account->data.minecraftEntitlement.canPlayMinecraft = true;
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]")); account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
account->data.minecraftProfile.name = username; account->data.minecraftProfile.name = username;
account->data.minecraftProfile.validity = Katabasis::Validity::Certain; account->data.minecraftProfile.validity = Validity::Certain;
return account; return account;
} }
@ -122,23 +119,11 @@ QPixmap MinecraftAccount::getFace() const
return skin.scaled(64, 64, Qt::KeepAspectRatio); return skin.scaled(64, 64, Qt::KeepAspectRatio);
} }
shared_qobject_ptr<AuthFlow> MinecraftAccount::loginMSA() shared_qobject_ptr<AuthFlow> MinecraftAccount::login()
{ {
Q_ASSERT(m_currentTask.get() == nullptr); Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new MSAInteractive(&data)); m_currentTask.reset(new AuthFlow(&data, false, this));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AuthFlow> MinecraftAccount::loginOffline()
{
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new OfflineLogin(&data));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
@ -152,11 +137,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::refresh()
return m_currentTask; return m_currentTask;
} }
if (data.type == AccountType::MSA) { m_currentTask.reset(new AuthFlow(&data, true, this));
m_currentTask.reset(new MSASilent(&data));
} else {
m_currentTask.reset(new OfflineLogin(&data));
}
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
@ -191,17 +172,17 @@ void MinecraftAccount::authFailed(QString reason)
if (accountType() == AccountType::MSA) { if (accountType() == AccountType::MSA) {
data.msaToken.token = QString(); data.msaToken.token = QString();
data.msaToken.refresh_token = QString(); data.msaToken.refresh_token = QString();
data.msaToken.validity = Katabasis::Validity::None; data.msaToken.validity = Validity::None;
data.validity_ = Katabasis::Validity::None; data.validity_ = Validity::None;
} else { } else {
data.yggdrasilToken.token = QString(); data.yggdrasilToken.token = QString();
data.yggdrasilToken.validity = Katabasis::Validity::None; data.yggdrasilToken.validity = Validity::None;
data.validity_ = Katabasis::Validity::None; data.validity_ = Validity::None;
} }
emit changed(); emit changed();
} break; } break;
case AccountTaskState::STATE_FAILED_GONE: { case AccountTaskState::STATE_FAILED_GONE: {
data.validity_ = Katabasis::Validity::None; data.validity_ = Validity::None;
emit changed(); emit changed();
} break; } break;
case AccountTaskState::STATE_CREATED: case AccountTaskState::STATE_CREATED:
@ -231,13 +212,13 @@ bool MinecraftAccount::shouldRefresh() const
return false; return false;
} }
switch (data.validity_) { switch (data.validity_) {
case Katabasis::Validity::Certain: { case Validity::Certain: {
break; break;
} }
case Katabasis::Validity::None: { case Validity::None: {
return false; return false;
} }
case Katabasis::Validity::Assumed: { case Validity::Assumed: {
return true; return true;
} }
} }

View File

@ -47,7 +47,7 @@
#include "AuthSession.h" #include "AuthSession.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "Usable.h" #include "Usable.h"
#include "minecraft/auth/flows/AuthFlow.h" #include "minecraft/auth/AuthFlow.h"
class Task; class Task;
class MinecraftAccount; class MinecraftAccount;
@ -95,9 +95,7 @@ class MinecraftAccount : public QObject, public Usable {
QJsonObject saveToJson() const; QJsonObject saveToJson() const;
public: /* manipulation */ public: /* manipulation */
shared_qobject_ptr<AuthFlow> loginMSA(); shared_qobject_ptr<AuthFlow> login();
shared_qobject_ptr<AuthFlow> loginOffline();
shared_qobject_ptr<AuthFlow> refresh(); shared_qobject_ptr<AuthFlow> refresh();

View File

@ -79,7 +79,7 @@ bool getBool(QJsonValue value, bool& out)
// 2148916238 = child account not linked to a family // 2148916238 = child account not linked to a family
*/ */
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name) bool parseXTokenResponse(QByteArray& data, Token& output, QString name)
{ {
qDebug() << "Parsing" << name << ":"; qDebug() << "Parsing" << name << ":";
qCDebug(authCredentials()) << data; qCDebug(authCredentials()) << data;
@ -135,7 +135,7 @@ bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString nam
qWarning() << "Missing uhs"; qWarning() << "Missing uhs";
return false; return false;
} }
output.validity = Katabasis::Validity::Certain; output.validity = Validity::Certain;
qDebug() << name << "is valid."; qDebug() << name << "is valid.";
return true; return true;
} }
@ -213,7 +213,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
output.capes[capeOut.id] = capeOut; output.capes[capeOut.id] = capeOut;
} }
output.currentCape = currentCape; output.currentCape = currentCape;
output.validity = Katabasis::Validity::Certain; output.validity = Validity::Certain;
return true; return true;
} }
@ -388,7 +388,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
output.currentCape = capeOut.alias; output.currentCape = capeOut.alias;
} }
output.validity = Katabasis::Validity::Certain; output.validity = Validity::Certain;
return true; return true;
} }
@ -422,7 +422,7 @@ bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output)
output.ownsMinecraft = true; output.ownsMinecraft = true;
} }
} }
output.validity = Katabasis::Validity::Certain; output.validity = Validity::Certain;
return true; return true;
} }
@ -456,7 +456,7 @@ bool parseRolloutResponse(QByteArray& data, bool& result)
return true; return true;
} }
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output) bool parseMojangResponse(QByteArray& data, Token& output)
{ {
QJsonParseError jsonError; QJsonParseError jsonError;
qDebug() << "Parsing Mojang response..."; qDebug() << "Parsing Mojang response...";
@ -488,7 +488,7 @@ bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
qWarning() << "access_token is not valid"; qWarning() << "access_token is not valid";
return false; return false;
} }
output.validity = Katabasis::Validity::Certain; output.validity = Validity::Certain;
qDebug() << "Mojang response is valid."; qDebug() << "Mojang response is valid.";
return true; return true;
} }

View File

@ -9,8 +9,8 @@ bool getNumber(QJsonValue value, double& out);
bool getNumber(QJsonValue value, int64_t& out); bool getNumber(QJsonValue value, int64_t& out);
bool getBool(QJsonValue value, bool& out); bool getBool(QJsonValue value, bool& out);
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name); bool parseXTokenResponse(QByteArray& data, Token& output, QString name);
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output); bool parseMojangResponse(QByteArray& data, Token& output);
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output); bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output); bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);

View File

@ -1,36 +0,0 @@
#include "MSA.h"
#include "minecraft/auth/steps/EntitlementsStep.h"
#include "minecraft/auth/steps/GetSkinStep.h"
#include "minecraft/auth/steps/LauncherLoginStep.h"
#include "minecraft/auth/steps/MSAStep.h"
#include "minecraft/auth/steps/MinecraftProfileStep.h"
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
#include "minecraft/auth/steps/XboxProfileStep.h"
#include "minecraft/auth/steps/XboxUserStep.h"
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Refresh));
m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
m_steps.append(makeShared<LauncherLoginStep>(m_data));
m_steps.append(makeShared<XboxProfileStep>(m_data));
m_steps.append(makeShared<EntitlementsStep>(m_data));
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
m_steps.append(makeShared<GetSkinStep>(m_data));
}
MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Login));
m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
m_steps.append(makeShared<LauncherLoginStep>(m_data));
m_steps.append(makeShared<XboxProfileStep>(m_data));
m_steps.append(makeShared<EntitlementsStep>(m_data));
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
m_steps.append(makeShared<GetSkinStep>(m_data));
}

View File

@ -1,14 +0,0 @@
#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);
};

View File

@ -1,8 +0,0 @@
#include "Offline.h"
#include "minecraft/auth/steps/OfflineStep.h"
OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<OfflineStep>(m_data));
}

View File

@ -1,8 +0,0 @@
#pragma once
#include "AuthFlow.h"
class OfflineLogin : public AuthFlow {
Q_OBJECT
public:
explicit OfflineLogin(AccountData* data, QObject* parent = 0);
};

View File

@ -37,12 +37,11 @@
#include <QtNetworkAuth/qoauthhttpserverreplyhandler.h> #include <QtNetworkAuth/qoauthhttpserverreplyhandler.h>
#include <QAbstractOAuth2> #include <QAbstractOAuth2>
#include <QDesktopServices>
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Application.h" #include "Application.h"
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
{ {
m_clientId = APPLICATION->getMSAClientID(); m_clientId = APPLICATION->getMSAClientID();
@ -63,7 +62,7 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac
m_data->msaToken.token = oauth2.token(); m_data->msaToken.token = oauth2.token();
emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
}); });
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &QDesktopServices::openUrl); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) { connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed.")); emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
}); });
@ -82,30 +81,25 @@ QString MSAStep::describe()
void MSAStep::perform() void MSAStep::perform()
{ {
switch (m_action) { if (m_silent) {
case Refresh: { if (m_data->msaClientID != m_clientId) {
if (m_data->msaClientID != m_clientId) { emit finished(AccountTaskState::STATE_DISABLED,
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed."));
tr("Microsoft user authentication failed - client identification has changed."));
}
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
oauth2.refreshAccessToken();
return;
} }
case Login: { oauth2.setRefreshToken(m_data->msaToken.refresh_token);
oauth2.refreshAccessToken();
} else {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) {
#else #else
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) {
#endif #endif
map->insert("prompt", "select_account"); map->insert("prompt", "select_account");
map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d"); map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d");
}); });
*m_data = AccountData(); *m_data = AccountData();
m_data->msaClientID = m_clientId; m_data->msaClientID = m_clientId;
oauth2.grant(); oauth2.grant();
return;
}
} }
} }

View File

@ -42,18 +42,18 @@
class MSAStep : public AuthStep { class MSAStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
enum Action { Refresh, Login }; explicit MSAStep(AccountData* data, bool silent = false);
public:
explicit MSAStep(AccountData* data, Action action);
virtual ~MSAStep() noexcept = default; virtual ~MSAStep() noexcept = default;
void perform() override; void perform() override;
QString describe() override; QString describe() override;
signals:
void authorizeWithBrowser(const QUrl& url);
private: private:
Action m_action; bool m_silent;
QString m_clientId; QString m_clientId;
QOAuth2AuthorizationCodeFlow oauth2; QOAuth2AuthorizationCodeFlow oauth2;
}; };

View File

@ -1,13 +0,0 @@
#include "OfflineStep.h"
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
QString OfflineStep::describe()
{
return tr("Creating offline account.");
}
void OfflineStep::perform()
{
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));
}

View File

@ -1,15 +0,0 @@
#pragma once
#include <QObject>
#include "minecraft/auth/AuthStep.h"
class OfflineStep : public AuthStep {
Q_OBJECT
public:
explicit OfflineStep(AccountData* data);
virtual ~OfflineStep() noexcept = default;
void perform() override;
QString describe() override;
};

View File

@ -11,7 +11,7 @@
#include "net/StaticHeaderProxy.h" #include "net/StaticHeaderProxy.h"
#include "net/Upload.h" #include "net/Upload.h"
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind) XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind)
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
{} {}
@ -72,7 +72,7 @@ void XboxAuthorizationStep::onRequestDone()
return; return;
} }
Katabasis::Token temp; Token temp;
if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) { if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)); tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));

View File

@ -9,7 +9,7 @@ class XboxAuthorizationStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind); explicit XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind);
virtual ~XboxAuthorizationStep() noexcept = default; virtual ~XboxAuthorizationStep() noexcept = default;
void perform() override; void perform() override;
@ -23,7 +23,7 @@ class XboxAuthorizationStep : public AuthStep {
void onRequestDone(); void onRequestDone();
private: private:
Katabasis::Token* m_token; Token* m_token;
QString m_relyingParty; QString m_relyingParty;
QString m_authorizationKind; QString m_authorizationKind;

View File

@ -60,7 +60,7 @@ void XboxUserStep::onRequestDone()
return; return;
} }
Katabasis::Token temp; Token temp;
if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) { if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) {
qWarning() << "Could not parse user authentication response..."; qWarning() << "Could not parse user authentication response...";
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood.")); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));

View File

@ -37,7 +37,7 @@
#include "ui_MSALoginDialog.h" #include "ui_MSALoginDialog.h"
#include "DesktopServices.h" #include "DesktopServices.h"
#include "minecraft/auth/flows/AuthFlow.h" #include "minecraft/auth/AuthFlow.h"
#include <QApplication> #include <QApplication>
#include <QClipboard> #include <QClipboard>
@ -47,26 +47,24 @@
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog) MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->progressBar->setVisible(false);
ui->actionButton->setVisible(false);
// ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); ui->cancel->setEnabled(false);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); ui->link->setVisible(false);
ui->copy->setVisible(false);
connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject);
connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl);
} }
int MSALoginDialog::exec() int MSALoginDialog::exec()
{ {
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
// Setup the login task and start it // Setup the login task and start it
m_account = MinecraftAccount::createBlankMSA(); m_account = MinecraftAccount::createBlankMSA();
m_loginTask = m_account->loginMSA(); m_loginTask = m_account->login();
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress); connect(m_loginTask.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
m_loginTask->start(); m_loginTask->start();
return QDialog::exec(); return QDialog::exec();
@ -77,11 +75,6 @@ MSALoginDialog::~MSALoginDialog()
delete ui; delete ui;
} }
void MSALoginDialog::setUserInputsEnabled(bool enable)
{
ui->buttonBox->setEnabled(enable);
}
void MSALoginDialog::onTaskFailed(const QString& reason) void MSALoginDialog::onTaskFailed(const QString& reason)
{ {
// Set message // Set message
@ -94,12 +87,7 @@ void MSALoginDialog::onTaskFailed(const QString& reason)
processed += "<br />"; processed += "<br />";
} }
} }
ui->label->setText(processed); ui->message->setText(processed);
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
ui->actionButton->setVisible(false);
} }
void MSALoginDialog::onTaskSucceeded() void MSALoginDialog::onTaskSucceeded()
@ -109,22 +97,38 @@ void MSALoginDialog::onTaskSucceeded()
void MSALoginDialog::onTaskStatus(const QString& status) void MSALoginDialog::onTaskStatus(const QString& status)
{ {
ui->label->setText(status); ui->message->setText(status);
} ui->cancel->setEnabled(false);
ui->link->setVisible(false);
void MSALoginDialog::onTaskProgress(qint64 current, qint64 total) ui->copy->setVisible(false);
{
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(current);
} }
// Public interface // Public interface
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg) MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg)
{ {
MSALoginDialog dlg(parent); MSALoginDialog dlg(parent);
dlg.ui->label->setText(msg); dlg.ui->message->setText(msg);
if (dlg.exec() == QDialog::Accepted) { if (dlg.exec() == QDialog::Accepted) {
return dlg.m_account; return dlg.m_account;
} }
return nullptr; return nullptr;
} }
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
{
ui->cancel->setEnabled(true);
ui->link->setVisible(true);
ui->copy->setVisible(true);
DesktopServices::openUrl(url);
ui->link->setText(url.toDisplayString());
ui->message->setText(
tr("Browser opened to complete the login process."
"<br /><br />"
"If your browser hasn't opened, please manually open the bellow link in your browser:"));
}
void MSALoginDialog::copyUrl()
{
QClipboard* cb = QApplication::clipboard();
cb->setText(ui->link->text());
}

View File

@ -18,8 +18,8 @@
#include <QtCore/QEventLoop> #include <QtCore/QEventLoop>
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include "minecraft/auth/AuthFlow.h"
#include "minecraft/auth/MinecraftAccount.h" #include "minecraft/auth/MinecraftAccount.h"
#include "minecraft/auth/flows/AuthFlow.h"
namespace Ui { namespace Ui {
class MSALoginDialog; class MSALoginDialog;
@ -37,13 +37,12 @@ class MSALoginDialog : public QDialog {
private: private:
explicit MSALoginDialog(QWidget* parent = 0); explicit MSALoginDialog(QWidget* parent = 0);
void setUserInputsEnabled(bool enable);
protected slots: protected slots:
void onTaskFailed(const QString& reason); void onTaskFailed(const QString& reason);
void onTaskSucceeded(); void onTaskSucceeded();
void onTaskStatus(const QString& status); void onTaskStatus(const QString& status);
void onTaskProgress(qint64 current, qint64 total); void authorizeWithBrowser(const QUrl& url);
void copyUrl();
private: private:
Ui::MSALoginDialog* ui; Ui::MSALoginDialog* ui;

View File

@ -21,11 +21,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="message">
<property name="text"> <property name="text">
<string notr="true">Message label placeholder. <string notr="true"/>
aaaaa</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
@ -39,36 +37,33 @@ aaaaa</string>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QProgressBar" name="progressBar"> <layout class="QHBoxLayout" name="linkLayout">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QPushButton" name="actionButton"> <widget class="QLineEdit" name="link">
<property name="text"> <property name="readOnly">
<string>Open page and copy code</string> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QPushButton" name="copy">
<property name="orientation"> <property name="text">
<enum>Qt::Horizontal</enum> <string/>
</property> </property>
<property name="standardButtons"> <property name="icon">
<set>QDialogButtonBox::Cancel</set> <iconset theme="copy"/>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QPushButton" name="cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -26,7 +26,7 @@ void OfflineLoginDialog::accept()
// Setup the login task and start it // Setup the login task and start it
m_account = MinecraftAccount::createOffline(ui->userTextBox->text()); m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
m_loginTask = m_account->loginOffline(); m_loginTask = m_account->login();
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed); connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus); connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);