aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/auth
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/auth')
-rw-r--r--launcher/minecraft/auth/AccountData.cpp4
-rw-r--r--launcher/minecraft/auth/AccountData.h17
-rw-r--r--launcher/minecraft/auth/AccountList.cpp171
-rw-r--r--launcher/minecraft/auth/AccountList.h31
-rw-r--r--launcher/minecraft/auth/AccountTask.cpp75
-rw-r--r--launcher/minecraft/auth/AccountTask.h72
-rw-r--r--launcher/minecraft/auth/AuthRequest.cpp (renamed from launcher/minecraft/auth/flows/AuthRequest.cpp)10
-rw-r--r--launcher/minecraft/auth/AuthRequest.h (renamed from launcher/minecraft/auth/flows/AuthRequest.h)8
-rw-r--r--launcher/minecraft/auth/AuthSession.cpp5
-rw-r--r--launcher/minecraft/auth/AuthSession.h4
-rw-r--r--launcher/minecraft/auth/AuthStep.cpp7
-rw-r--r--launcher/minecraft/auth/AuthStep.h33
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp261
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.h41
-rw-r--r--launcher/minecraft/auth/Parsers.cpp (renamed from launcher/minecraft/auth/flows/Parsers.cpp)6
-rw-r--r--launcher/minecraft/auth/Parsers.h (renamed from launcher/minecraft/auth/flows/Parsers.h)4
-rw-r--r--launcher/minecraft/auth/Yggdrasil.cpp (renamed from launcher/minecraft/auth/flows/Yggdrasil.cpp)34
-rw-r--r--launcher/minecraft/auth/Yggdrasil.h (renamed from launcher/minecraft/auth/flows/Yggdrasil.h)22
-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/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/steps/EntitlementsStep.cpp53
-rw-r--r--launcher/minecraft/auth/steps/EntitlementsStep.h25
-rw-r--r--launcher/minecraft/auth/steps/GetSkinStep.cpp43
-rw-r--r--launcher/minecraft/auth/steps/GetSkinStep.h22
-rw-r--r--launcher/minecraft/auth/steps/LauncherLoginStep.cpp78
-rw-r--r--launcher/minecraft/auth/steps/LauncherLoginStep.h22
-rw-r--r--launcher/minecraft/auth/steps/MSAStep.cpp111
-rw-r--r--launcher/minecraft/auth/steps/MSAStep.h32
-rw-r--r--launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp45
-rw-r--r--launcher/minecraft/auth/steps/MigrationEligibilityStep.h22
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStep.cpp91
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStep.h22
-rw-r--r--launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp158
-rw-r--r--launcher/minecraft/auth/steps/XboxAuthorizationStep.h34
-rw-r--r--launcher/minecraft/auth/steps/XboxProfileStep.cpp73
-rw-r--r--launcher/minecraft/auth/steps/XboxProfileStep.h22
-rw-r--r--launcher/minecraft/auth/steps/XboxUserStep.cpp68
-rw-r--r--launcher/minecraft/auth/steps/XboxUserStep.h22
-rw-r--r--launcher/minecraft/auth/steps/YggdrasilStep.cpp51
-rw-r--r--launcher/minecraft/auth/steps/YggdrasilStep.h28
54 files changed, 1729 insertions, 1233 deletions
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
index 8aa4e37f..7526c951 100644
--- a/launcher/minecraft/auth/AccountData.cpp
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -438,3 +438,7 @@ QString AccountData::accountDisplayString() const {
}
}
}
+
+QString AccountData::lastError() const {
+ return errorString;
+}
diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h
index 09cd2c73..abf84e43 100644
--- a/launcher/minecraft/auth/AccountData.h
+++ b/launcher/minecraft/auth/AccountData.h
@@ -41,6 +41,16 @@ enum class AccountType {
Mojang
};
+enum class AccountState {
+ Unchecked,
+ Offline,
+ Working,
+ Online,
+ Errored,
+ Expired,
+ Gone
+};
+
struct AccountData {
QJsonObject saveState() const;
bool resumeStateFromV2(QJsonObject data);
@@ -64,6 +74,8 @@ struct AccountData {
QString profileId() const;
QString profileName() const;
+ QString lastError() const;
+
AccountType type = AccountType::MSA;
bool legacy = false;
bool canMigrateToMSA = false;
@@ -77,4 +89,9 @@ struct AccountData {
MinecraftProfile minecraftProfile;
MinecraftEntitlement minecraftEntitlement;
Katabasis::Validity validity_ = Katabasis::Validity::None;
+
+ // runtime only information (not saved with the account)
+ QString internalId;
+ QString errorString;
+ AccountState accountState = AccountState::Unchecked;
};
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index d7537345..ef8b435d 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -15,6 +15,7 @@
#include "AccountList.h"
#include "AccountData.h"
+#include "AccountTask.h"
#include <QIODevice>
#include <QFile>
@@ -24,18 +25,28 @@
#include <QJsonObject>
#include <QJsonParseError>
#include <QDir>
+#include <QTimer>
#include <QDebug>
#include <FileSystem.h>
#include <QSaveFile>
+#include <chrono>
+
enum AccountListVersion {
MojangOnly = 2,
MojangMSA = 3
};
-AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) { }
+AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) {
+ m_refreshTimer = new QTimer(this);
+ m_refreshTimer->setSingleShot(true);
+ connect(m_refreshTimer, &QTimer::timeout, this, &AccountList::fillQueue);
+ m_nextTimer = new QTimer(this);
+ m_nextTimer->setSingleShot(true);
+ connect(m_nextTimer, &QTimer::timeout, this, &AccountList::tryNext);
+}
AccountList::~AccountList() noexcept {}
@@ -78,9 +89,18 @@ QStringList AccountList::profileNames() const {
void AccountList::addAccount(const MinecraftAccountPtr account)
{
+ // NOTE: Do not allow adding something that's already there
+ if(m_accounts.contains(account)) {
+ return;
+ }
+
+ // hook up notifications for changes in the account
+ connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
+ connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
+
+ // override/replace existing account with the same profileId
auto profileId = account->profileId();
if(profileId.size()) {
- // override/replace existing account with the same profileId
auto existingAccount = findAccountByProfileId(profileId);
if(existingAccount != -1) {
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
@@ -88,6 +108,8 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
if(m_defaultAccount == existingAccountPtr) {
m_defaultAccount = account;
}
+ // disconnect notifications for changes in the account being replaced
+ existingAccountPtr->disconnect(this);
emit dataChanged(index(existingAccount), index(existingAccount, columnCount(QModelIndex()) - 1));
onListChanged();
return;
@@ -97,8 +119,6 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
// if we don't have this profileId yet, add the account to the end
int row = m_accounts.count();
beginInsertRows(QModelIndex(), row, row);
- connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
- connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
m_accounts.append(account);
endInsertRows();
onListChanged();
@@ -115,6 +135,8 @@ void AccountList::removeAccount(QModelIndex index)
m_defaultAccount = nullptr;
onDefaultAccountChanged();
}
+ account->disconnect(this);
+
beginRemoveRows(QModelIndex(), row, row);
m_accounts.removeAt(index.row());
endRemoveRows();
@@ -193,6 +215,12 @@ void AccountList::accountActivityChanged(bool active)
}
if(found) {
emit listActivityChanged();
+ if(active) {
+ beginActivity();
+ }
+ else {
+ endActivity();
+ }
}
}
@@ -244,13 +272,29 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
}
case StatusColumn: {
- if(account->isActive()) {
- return tr("Working", "Account status");
- }
- if(account->isExpired()) {
- return tr("Expired", "Account status");
+ switch(account->accountState()) {
+ case AccountState::Unchecked: {
+ return tr("Unchecked", "Account status");
+ }
+ case AccountState::Offline: {
+ return tr("Offline", "Account status");
+ }
+ case AccountState::Online: {
+ return tr("Online", "Account status");
+ }
+ case AccountState::Working: {
+ return tr("Working", "Account status");
+ }
+ case AccountState::Errored: {
+ return tr("Errored", "Account status");
+ }
+ case AccountState::Expired: {
+ return tr("Expired", "Account status");
+ }
+ case AccountState::Gone: {
+ return tr("Gone", "Account status");
+ }
}
- return tr("Ready", "Account status");
}
case ProfileNameColumn: {
@@ -583,10 +627,113 @@ void AccountList::setListFilePath(QString path, bool autosave)
bool AccountList::anyAccountIsValid()
{
- for(auto account:m_accounts)
+ for(auto account: m_accounts)
{
- if(account->accountStatus() != NotVerified)
+ if(account->ownsMinecraft()) {
return true;
+ }
}
return false;
}
+
+void AccountList::fillQueue() {
+
+ if(m_defaultAccount && m_defaultAccount->shouldRefresh()) {
+ auto idToRefresh = m_defaultAccount->internalId();
+ m_refreshQueue.push_back(idToRefresh);
+ qDebug() << "AccountList: Queued default account with internal ID " << idToRefresh << " to refresh first";
+ }
+
+ for(int i = 0; i < count(); i++) {
+ auto account = at(i);
+ if(account == m_defaultAccount) {
+ continue;
+ }
+
+ if(account->shouldRefresh()) {
+ auto idToRefresh = account->internalId();
+ queueRefresh(idToRefresh);
+ }
+ }
+ tryNext();
+}
+
+void AccountList::requestRefresh(QString accountId) {
+ auto index = m_refreshQueue.indexOf(accountId);
+ if(index != -1) {
+ m_refreshQueue.removeAt(index);
+ }
+ m_refreshQueue.push_front(accountId);
+ qDebug() << "AccountList: Pushed account with internal ID " << accountId << " to the front of the queue";
+ if(!isActive()) {
+ tryNext();
+ }
+}
+
+void AccountList::queueRefresh(QString accountId) {
+ if(m_refreshQueue.indexOf(accountId) != -1) {
+ return;
+ }
+ m_refreshQueue.push_back(accountId);
+ qDebug() << "AccountList: Queued account with internal ID " << accountId << " to refresh";
+}
+
+
+void AccountList::tryNext() {
+ while (m_refreshQueue.length()) {
+ auto accountId = m_refreshQueue.front();
+ m_refreshQueue.pop_front();
+ for(int i = 0; i < count(); i++) {
+ auto account = at(i);
+ if(account->internalId() == accountId) {
+ m_currentTask = account->refresh();
+ if(m_currentTask) {
+ connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded);
+ connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed);
+ m_currentTask->start();
+ qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID " << accountId;
+ return;
+ }
+ }
+ }
+ qDebug() << "RefreshSchedule: Account with with internal ID " << accountId << " not found.";
+ }
+ // if we get here, no account needed refreshing. Schedule refresh in an hour.
+ m_refreshTimer->start(1000 * 3600);
+}
+
+void AccountList::authSucceeded() {
+ qDebug() << "RefreshSchedule: Background account refresh succeeded";
+ m_currentTask.reset();
+ m_nextTimer->start(1000 * 20);
+}
+
+void AccountList::authFailed(QString reason) {
+ qDebug() << "RefreshSchedule: Background account refresh failed: " << reason;
+ m_currentTask.reset();
+ m_nextTimer->start(1000 * 20);
+}
+
+bool AccountList::isActive() const {
+ return m_activityCount != 0;
+}
+
+void AccountList::beginActivity() {
+ bool activating = m_activityCount == 0;
+ m_activityCount++;
+ if(activating) {
+ emit activityChanged(true);
+ }
+}
+
+void AccountList::endActivity() {
+ if(m_activityCount == 0) {
+ qWarning() << m_name << " - Activity count would become below zero";
+ return;
+ }
+ bool deactivating = m_activityCount == 1;
+ m_activityCount--;
+ if(deactivating) {
+ emit activityChanged(false);
+ }
+}
diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h
index 08004628..fa1e7431 100644
--- a/launcher/minecraft/auth/AccountList.h
+++ b/launcher/minecraft/auth/AccountList.h
@@ -67,6 +67,11 @@ public:
MinecraftAccountPtr getAccountByProfileName(const QString &profileName) const;
QStringList profileNames() const;
+ // requesting a refresh pushes it to the front of the queue
+ void requestRefresh(QString accountId);
+ // queuing a refresh will let it go to the back of the queue (unless it's somewhere inside the queue already)
+ void queueRefresh(QString accountId);
+
/*!
* Sets the path to load/save the list file from/to.
* If autosave is true, this list will automatically save to the given path whenever it changes.
@@ -85,10 +90,20 @@ public:
void setDefaultAccount(MinecraftAccountPtr profileId);
bool anyAccountIsValid();
+ bool isActive() const;
+
+protected:
+ void beginActivity();
+ void endActivity();
+
+private:
+ const char* m_name;
+ uint32_t m_activityCount = 0;
signals:
void listChanged();
void listActivityChanged();
void defaultAccountChanged();
+ void activityChanged(bool active);
public slots:
/**
@@ -101,7 +116,23 @@ public slots:
*/
void accountActivityChanged(bool active);
+ /**
+ * This is initially to run background account refresh tasks, or on a hourly timer
+ */
+ void fillQueue();
+
+private slots:
+ void tryNext();
+
+ void authSucceeded();
+ void authFailed(QString reason);
+
protected:
+ QList<QString> m_refreshQueue;
+ QTimer *m_refreshTimer;
+ QTimer *m_nextTimer;
+ shared_qobject_ptr<AccountTask> m_currentTask;
+
/*!
* Called whenever the list changes.
* This emits the listChanged() signal and autosaves the list (if autosave is enabled).
diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp
index 25d753de..98d8d94d 100644
--- a/launcher/minecraft/auth/AccountTask.cpp
+++ b/launcher/minecraft/auth/AccountTask.cpp
@@ -28,40 +28,79 @@
AccountTask::AccountTask(AccountData *data, QObject *parent)
: Task(parent), m_data(data)
{
- changeState(STATE_CREATED);
+ changeState(AccountTaskState::STATE_CREATED);
}
QString AccountTask::getStateMessage() const
{
- switch (m_accountState)
+ switch (m_taskState)
{
- case STATE_CREATED:
+ case AccountTaskState::STATE_CREATED:
return "Waiting...";
- case STATE_WORKING:
+ case AccountTaskState::STATE_WORKING:
return tr("Sending request to auth servers...");
- case STATE_SUCCEEDED:
+ case AccountTaskState::STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
- case STATE_FAILED_SOFT:
+ case AccountTaskState::STATE_OFFLINE:
return tr("Failed to contact the authentication server.");
- case STATE_FAILED_HARD:
- return tr("Failed to authenticate.");
- case STATE_FAILED_GONE:
+ case AccountTaskState::STATE_FAILED_SOFT:
+ return tr("Encountered an error during authentication.");
+ case AccountTaskState::STATE_FAILED_HARD:
+ return tr("Failed to authenticate. The session has expired.");
+ case AccountTaskState::STATE_FAILED_GONE:
return tr("Failed to authenticate. The account no longer exists.");
default:
return tr("...");
}
}
-void AccountTask::changeState(AccountTask::State newState, QString reason)
+bool AccountTask::changeState(AccountTaskState newState, QString reason)
{
- m_accountState = newState;
+ m_taskState = newState;
setStatus(getStateMessage());
- if (newState == STATE_SUCCEEDED)
- {
- emitSucceeded();
- }
- else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT || newState == STATE_FAILED_GONE)
- {
- emitFailed(reason);
+ switch(newState) {
+ case AccountTaskState::STATE_CREATED: {
+ m_data->errorString.clear();
+ return true;
+ }
+ case AccountTaskState::STATE_WORKING: {
+ m_data->accountState = AccountState::Working;
+ return true;
+ }
+ case AccountTaskState::STATE_SUCCEEDED: {
+ m_data->accountState = AccountState::Online;
+ emitSucceeded();
+ return false;
+ }
+ case AccountTaskState::STATE_OFFLINE: {
+ m_data->errorString = reason;
+ m_data->accountState = AccountState::Offline;
+ emitFailed(reason);
+ return false;
+ }
+ case AccountTaskState::STATE_FAILED_SOFT: {
+ m_data->errorString = reason;
+ m_data->accountState = AccountState::Errored;
+ emitFailed(reason);
+ return false;
+ }
+ case AccountTaskState::STATE_FAILED_HARD: {
+ m_data->errorString = reason;
+ m_data->accountState = AccountState::Expired;
+ emitFailed(reason);
+ return false;
+ }
+ case AccountTaskState::STATE_FAILED_GONE: {
+ m_data->errorString = reason;
+ m_data->accountState = AccountState::Gone;
+ emitFailed(reason);
+ return false;
+ }
+ default: {
+ QString error = tr("Unknown account task state: %1").arg(int(newState));
+ m_data->accountState = AccountState::Errored;
+ emitFailed(error);
+ return false;
+ }
}
}
diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h
index 4f3bd52a..dac3f1b5 100644
--- a/launcher/minecraft/auth/AccountTask.h
+++ b/launcher/minecraft/auth/AccountTask.h
@@ -26,62 +26,32 @@
class QNetworkReply;
+/**
+ * Enum for describing the state of the current task.
+ * Used by the getStateMessage function to determine what the status message should be.
+ */
+enum class AccountTaskState
+{
+ STATE_CREATED,
+ STATE_WORKING,
+ STATE_SUCCEEDED,
+ STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
+ 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_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
+};
+
class AccountTask : public Task
{
- friend class AuthContext;
Q_OBJECT
public:
explicit AccountTask(AccountData * data, QObject *parent = 0);
virtual ~AccountTask() {};
- /**
- * assign a session to this task. the session will be filled with required infomration
- * upon completion
- */
- void assignSession(AuthSessionPtr session)
- {
- m_session = session;
- }
-
- /// get the assigned session for filling with information.
- AuthSessionPtr getAssignedSession()
- {
- return m_session;
- }
-
- /**
- * Class describing a Account error response.
- */
- struct Error
- {
- QString m_errorMessageShort;
- QString m_errorMessageVerbose;
- QString m_cause;
- };
-
- enum AbortedBy
- {
- BY_NOTHING,
- BY_USER,
- BY_TIMEOUT
- } m_aborted = BY_NOTHING;
-
- /**
- * Enum for describing the state of the current task.
- * Used by the getStateMessage function to determine what the status message should be.
- */
- enum State
- {
- STATE_CREATED,
- STATE_WORKING,
- STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated
- STATE_FAILED_HARD, //!< hard failure. auth is invalid
- STATE_FAILED_GONE, //!< hard failure. auth is invalid, and the account no longer exists
- STATE_SUCCEEDED
- } m_accountState = STATE_CREATED;
+ AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
- State a