GH-4071 Heavily refactor and rearchitect account system

This makes the account system much more modular
and makes it treat errors as something recoverable,
unless they come directly from the MSA refresh token
becoming invalid.
This commit is contained in:
Petr Mrázek
2021-12-04 01:18:05 +01:00
parent ffcef673de
commit 3c46d8a412
68 changed files with 2105 additions and 1446 deletions

View File

@ -16,7 +16,6 @@
*/
#include "MinecraftAccount.h"
#include "flows/AuthContext.h"
#include <QUuid>
#include <QJsonObject>
@ -28,14 +27,12 @@
#include <QDebug>
#include <QPainter>
#include "flows/MSASilent.h"
#include "flows/MSAInteractive.h"
#include "flows/MojangRefresh.h"
#include "flows/MojangLogin.h"
#include "flows/MSA.h"
#include "flows/Mojang.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
m_internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
}
@ -77,42 +74,10 @@ QJsonObject MinecraftAccount::saveToJson() const
return data.saveState();
}
AccountStatus MinecraftAccount::accountStatus() const {
if(data.type == AccountType::Mojang) {
if (data.accessToken().isEmpty()) {
return NotVerified;
}
else {
return Verified;
}
}
// MSA
// FIXME: this is extremely crude and probably wrong
if(data.msaToken.token.isEmpty()) {
return NotVerified;
}
else {
return Verified;
}
AccountState MinecraftAccount::accountState() const {
return data.accountState;
}
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")) {
@ -126,136 +91,51 @@ QPixmap MinecraftAccount::getFace() const {
}
shared_qobject_ptr<AccountTask> MinecraftAccount::login(AuthSessionPtr session, QString password)
{
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
Q_ASSERT(m_currentTask.get() == nullptr);
// take care of the true offline status
if (accountStatus() == NotVerified && password.isEmpty())
{
if (session)
{
session->status = AuthSession::RequiresPassword;
fillSession(session);
}
return nullptr;
}
if(accountStatus() == Verified && !session->wants_online)
{
session->status = AuthSession::PlayableOffline;
session->auth_server_online = false;
fillSession(session);
return nullptr;
}
else
{
if (password.isEmpty())
{
m_currentTask.reset(new MojangRefresh(&data));
}
else
{
m_currentTask.reset(new MojangLogin(&data, password));
}
m_currentTask->assignSession(session);
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
emit activityChanged(true);
}
m_currentTask.reset(new MojangLogin(&data, password));
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA(AuthSessionPtr session) {
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
Q_ASSERT(m_currentTask.get() == nullptr);
if(accountStatus() == Verified && !session->wants_online)
{
session->status = AuthSession::PlayableOffline;
session->auth_server_online = false;
fillSession(session);
return nullptr;
}
else
{
m_currentTask.reset(new MSAInteractive(&data));
m_currentTask->assignSession(session);
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
emit activityChanged(true);
}
m_currentTask.reset(new MSAInteractive(&data));
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh(AuthSessionPtr session) {
Q_ASSERT(m_currentTask.get() == nullptr);
// take care of the true offline status
if (accountStatus() == NotVerified)
{
if (session)
{
if(data.type == AccountType::MSA) {
session->status = AuthSession::RequiresOAuth;
}
else {
session->status = AuthSession::RequiresPassword;
}
fillSession(session);
}
return nullptr;
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
if(m_currentTask) {
return m_currentTask;
}
if(accountStatus() == Verified && !session->wants_online)
{
session->status = AuthSession::PlayableOffline;
session->auth_server_online = false;
fillSession(session);
return nullptr;
if(data.type == AccountType::MSA) {
m_currentTask.reset(new MSASilent(&data));
}
else {
m_currentTask.reset(new MojangRefresh(&data));
}
else
{
if(data.type == AccountType::MSA) {
m_currentTask.reset(new MSASilent(&data));
}
else {
m_currentTask.reset(new MojangRefresh(&data));
}
m_currentTask->assignSession(session);
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
emit activityChanged(true);
}
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask() {
return m_currentTask;
}
void MinecraftAccount::authSucceeded()
{
auto session = m_currentTask->getAssignedSession();
if (session)
{
/*
session->status = AuthSession::RequiresProfileSetup;
session->auth_server_online = true;
*/
if(data.profileId().size() == 0) {
session->status = AuthSession::RequiresProfileSetup;
}
else {
if(session->wants_online) {
session->status = AuthSession::PlayableOnline;
}
else {
session->status = AuthSession::PlayableOffline;
}
}
fillSession(session);
session->auth_server_online = true;
}
m_currentTask.reset();
emit changed();
emit activityChanged(false);
@ -263,62 +143,35 @@ void MinecraftAccount::authSucceeded()
void MinecraftAccount::authFailed(QString reason)
{
auto session = m_currentTask->getAssignedSession();
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
switch (m_currentTask->accountState()) {
case AccountTask::STATE_FAILED_SOFT: {
if (session)
{
if(accountStatus() == Verified) {
session->status = AuthSession::PlayableOffline;
}
else {
if(data.type == AccountType::MSA) {
session->status = AuthSession::RequiresOAuth;
}
else {
session->status = AuthSession::RequiresPassword;
}
}
session->auth_server_online = false;
fillSession(session);
}
switch (m_currentTask->taskState()) {
case AccountTaskState::STATE_OFFLINE:
case AccountTaskState::STATE_FAILED_SOFT: {
// NOTE: this doesn't do much. There was an error of some sort.
}
break;
case AccountTask::STATE_FAILED_HARD: {
// FIXME: MSA data clearing
data.yggdrasilToken.token = QString();
data.yggdrasilToken.validity = Katabasis::Validity::None;
case AccountTaskState::STATE_FAILED_HARD: {
if(isMSA()) {
data.msaToken.token = QString();
data.msaToken.refresh_token = QString();
data.msaToken.validity = Katabasis::Validity::None;
data.validity_ = Katabasis::Validity::None;
}
else {
data.yggdrasilToken.token = QString();
data.yggdrasilToken.validity = Katabasis::Validity::None;
data.validity_ = Katabasis::Validity::None;
}
emit changed();
}
break;
case AccountTaskState::STATE_FAILED_GONE: {
data.validity_ = Katabasis::Validity::None;
emit changed();
if (session)
{
if(data.type == AccountType::MSA) {
session->status = AuthSession::RequiresOAuth;
}
else {
session->status = AuthSession::RequiresPassword;
}
session->auth_server_online = true;
fillSession(session);
}
}
break;
case AccountTask::STATE_FAILED_GONE: {
data.validity_ = Katabasis::Validity::None;
emit changed();
if (session)
{
session->status = AuthSession::GoneOrMigrated;
session->auth_server_online = true;
fillSession(session);
}
}
break;
case AccountTask::STATE_CREATED:
case AccountTask::STATE_WORKING:
case AccountTask::STATE_SUCCEEDED: {
case AccountTaskState::STATE_CREATED:
case AccountTaskState::STATE_WORKING:
case AccountTaskState::STATE_SUCCEEDED: {
// Not reachable here, as they are not failures.
}
}
@ -366,6 +219,18 @@ bool MinecraftAccount::shouldRefresh() const {
void MinecraftAccount::fillSession(AuthSessionPtr session)
{
if(ownsMinecraft() && !hasProfile()) {
session->status = AuthSession::RequiresProfileSetup;
}
else {
if(session->wants_online) {
session->status = AuthSession::PlayableOnline;
}
else {
session->status = AuthSession::PlayableOffline;
}
}
// the user name. you have to have an user name
// FIXME: not with MSA
session->username = data.userName();