@@ -1234,7 +1234,6 @@ target_link_libraries(Launcher_logic
|
||||
tomlplusplus::tomlplusplus
|
||||
qdcss
|
||||
BuildConfig
|
||||
Katabasis
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
ghcFilesystem::ghc_filesystem
|
||||
)
|
||||
@@ -1252,6 +1251,7 @@ target_link_libraries(Launcher_logic
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::NetworkAuth
|
||||
${Launcher_QT_LIBS}
|
||||
)
|
||||
target_link_libraries(Launcher_logic
|
||||
@@ -1322,7 +1322,6 @@ if(Launcher_BUILD_UPDATER)
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
${Launcher_QT_LIBS}
|
||||
cmark::cmark
|
||||
Katabasis
|
||||
)
|
||||
|
||||
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||
|
||||
@@ -34,12 +34,43 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <katabasis/Bits.h>
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#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 };
|
||||
|
||||
struct Token {
|
||||
QDateTime issueInstant;
|
||||
QDateTime notAfter;
|
||||
QString token;
|
||||
QString refresh_token;
|
||||
QVariantMap extra;
|
||||
|
||||
Validity validity = Validity::None;
|
||||
bool persistent = true;
|
||||
};
|
||||
|
||||
} // namespace Katabasis
|
||||
|
||||
struct Skin {
|
||||
QString id;
|
||||
QString url;
|
||||
|
||||
@@ -37,8 +37,6 @@ class AuthStep : public QObject {
|
||||
|
||||
signals:
|
||||
void finished(AccountTaskState resultingState, QString message);
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
protected:
|
||||
AccountData* m_data;
|
||||
|
||||
@@ -21,6 +21,7 @@ void AuthFlow::succeed()
|
||||
void AuthFlow::executeTask()
|
||||
{
|
||||
if (m_currentStep) {
|
||||
emitFailed("No task");
|
||||
return;
|
||||
}
|
||||
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
||||
@@ -39,38 +40,10 @@ void AuthFlow::nextStep()
|
||||
qDebug() << "AuthFlow:" << m_currentStep->describe();
|
||||
m_steps.pop_front();
|
||||
connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished);
|
||||
connect(m_currentStep.get(), &AuthStep::showVerificationUriAndCode, this, &AuthFlow::showVerificationUriAndCode);
|
||||
connect(m_currentStep.get(), &AuthStep::hideVerificationUriAndCode, this, &AuthFlow::hideVerificationUriAndCode);
|
||||
|
||||
m_currentStep->perform();
|
||||
}
|
||||
|
||||
QString AuthFlow::getStateMessage() const
|
||||
{
|
||||
switch (m_taskState) {
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
return "Waiting...";
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
if (m_currentStep)
|
||||
return m_currentStep->describe();
|
||||
return tr("Working...");
|
||||
case AccountTaskState::STATE_SUCCEEDED:
|
||||
return tr("Authentication task succeeded.");
|
||||
case AccountTaskState::STATE_OFFLINE:
|
||||
return tr("Failed to contact the authentication server.");
|
||||
case AccountTaskState::STATE_DISABLED:
|
||||
return tr("Client ID has changed. New session needs to be created.");
|
||||
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 AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
||||
{
|
||||
if (changeState(resultingState, message)) {
|
||||
@@ -81,54 +54,61 @@ void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
||||
bool AuthFlow::changeState(AccountTaskState newState, QString reason)
|
||||
{
|
||||
m_taskState = newState;
|
||||
// FIXME: virtual method invoked in constructor.
|
||||
// We want that behavior, but maybe make it less weird?
|
||||
setStatus(getStateMessage());
|
||||
setDetails(reason);
|
||||
switch (newState) {
|
||||
case AccountTaskState::STATE_CREATED: {
|
||||
setStatus(tr("Waiting..."));
|
||||
m_data->errorString.clear();
|
||||
return true;
|
||||
}
|
||||
case AccountTaskState::STATE_WORKING: {
|
||||
setStatus(m_currentStep ? m_currentStep->describe() : tr("Working..."));
|
||||
m_data->accountState = AccountState::Working;
|
||||
return true;
|
||||
}
|
||||
case AccountTaskState::STATE_SUCCEEDED: {
|
||||
setStatus(tr("Authentication task succeeded."));
|
||||
m_data->accountState = AccountState::Online;
|
||||
emitSucceeded();
|
||||
return false;
|
||||
}
|
||||
case AccountTaskState::STATE_OFFLINE: {
|
||||
setStatus(tr("Failed to contact the authentication server."));
|
||||
m_data->errorString = reason;
|
||||
m_data->accountState = AccountState::Offline;
|
||||
emitFailed(reason);
|
||||
return false;
|
||||
}
|
||||
case AccountTaskState::STATE_DISABLED: {
|
||||
setStatus(tr("Client ID has changed. New session needs to be created."));
|
||||
m_data->errorString = reason;
|
||||
m_data->accountState = AccountState::Disabled;
|
||||
emitFailed(reason);
|
||||
return false;
|
||||
}
|
||||
case AccountTaskState::STATE_FAILED_SOFT: {
|
||||
setStatus(tr("Encountered an error during authentication."));
|
||||
m_data->errorString = reason;
|
||||
m_data->accountState = AccountState::Errored;
|
||||
emitFailed(reason);
|
||||
return false;
|
||||
}
|
||||
case AccountTaskState::STATE_FAILED_HARD: {
|
||||
setStatus(tr("Failed to authenticate. The session has expired."));
|
||||
m_data->errorString = reason;
|
||||
m_data->accountState = AccountState::Expired;
|
||||
emitFailed(reason);
|
||||
return false;
|
||||
}
|
||||
case AccountTaskState::STATE_FAILED_GONE: {
|
||||
setStatus(tr("Failed to authenticate. The account no longer exists."));
|
||||
m_data->errorString = reason;
|
||||
m_data->accountState = AccountState::Gone;
|
||||
emitFailed(reason);
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
setStatus(tr("..."));
|
||||
QString error = tr("Unknown account task state: %1").arg(int(newState));
|
||||
m_data->accountState = AccountState::Errored;
|
||||
emitFailed(error);
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include <katabasis/DeviceFlow.h>
|
||||
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
#include "tasks/Task.h"
|
||||
@@ -27,18 +25,9 @@ class AuthFlow : public Task {
|
||||
AccountTaskState taskState() { return m_taskState; }
|
||||
|
||||
signals:
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
void activityChanged(Katabasis::Activity activity);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns the state message for the given state.
|
||||
* Used to set the status message for the task.
|
||||
* Should be overridden by subclasses that want to change messages for a given state.
|
||||
*/
|
||||
virtual QString getStateMessage() const;
|
||||
void succeed();
|
||||
void nextStep();
|
||||
|
||||
|
||||
@@ -35,28 +35,44 @@
|
||||
|
||||
#include "MSAStep.h"
|
||||
|
||||
#include <QtNetworkAuth/qoauthhttpserverreplyhandler.h>
|
||||
#include <QAbstractOAuth2>
|
||||
#include <QDesktopServices>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Logging.h"
|
||||
|
||||
using OAuth2 = Katabasis::DeviceFlow;
|
||||
using Activity = Katabasis::Activity;
|
||||
|
||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action)
|
||||
{
|
||||
m_clientId = APPLICATION->getMSAClientID();
|
||||
OAuth2::Options opts;
|
||||
opts.scope = "XboxLive.signin offline_access";
|
||||
opts.clientIdentifier = m_clientId;
|
||||
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
|
||||
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
|
||||
|
||||
// FIXME: OAuth2 is not aware of our fancy shared pointers
|
||||
m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get());
|
||||
auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
|
||||
oauth2.setReplyHandler(replyHandler);
|
||||
oauth2.setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize"));
|
||||
oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token"));
|
||||
oauth2.setScope("XboxLive.SignIn XboxLive.offline_access");
|
||||
oauth2.setClientIdentifier(m_clientId);
|
||||
oauth2.setNetworkAccessManager(APPLICATION->network().get());
|
||||
|
||||
connect(m_oauth2, &OAuth2::activityChanged, this, &MSAStep::onOAuthActivityChanged);
|
||||
connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode);
|
||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] {
|
||||
m_data->msaClientID = oauth2.clientIdentifier();
|
||||
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||
m_data->msaToken.notAfter = oauth2.expirationAt();
|
||||
m_data->msaToken.extra = oauth2.extraTokens();
|
||||
m_data->msaToken.refresh_token = oauth2.refreshToken();
|
||||
m_data->msaToken.token = oauth2.token();
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
||||
});
|
||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &QDesktopServices::openUrl);
|
||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
|
||||
});
|
||||
|
||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
|
||||
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
|
||||
|
||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this,
|
||||
[this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; });
|
||||
}
|
||||
|
||||
QString MSAStep::describe()
|
||||
@@ -69,68 +85,22 @@ void MSAStep::perform()
|
||||
switch (m_action) {
|
||||
case Refresh: {
|
||||
if (m_data->msaClientID != m_clientId) {
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_DISABLED,
|
||||
tr("Microsoft user authentication failed - client identification has changed."));
|
||||
}
|
||||
m_oauth2->refresh();
|
||||
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
|
||||
oauth2.refreshAccessToken();
|
||||
return;
|
||||
}
|
||||
case Login: {
|
||||
QVariantMap extraOpts;
|
||||
extraOpts["prompt"] = "select_account";
|
||||
m_oauth2->setExtraRequestParams(extraOpts);
|
||||
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) {
|
||||
map->insert("prompt", "select_account");
|
||||
map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d");
|
||||
});
|
||||
|
||||
*m_data = AccountData();
|
||||
m_data->msaClientID = m_clientId;
|
||||
m_oauth2->login();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity)
|
||||
{
|
||||
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();
|
||||
QVariantMap extraTokens = m_oauth2->extraTokens();
|
||||
if (!extraTokens.isEmpty()) {
|
||||
qCDebug(authCredentials()) << "Extra tokens in response:";
|
||||
foreach (QString key, extraTokens.keys()) {
|
||||
qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key);
|
||||
}
|
||||
}
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
||||
return;
|
||||
}
|
||||
case Katabasis::Activity::FailedSoft: {
|
||||
// NOTE: soft error in the first step means 'offline'
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Microsoft user authentication ended with a network error."));
|
||||
return;
|
||||
}
|
||||
case Katabasis::Activity::FailedGone: {
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_FAILED_GONE, tr("Microsoft user authentication failed - user no longer exists."));
|
||||
return;
|
||||
}
|
||||
case Katabasis::Activity::FailedHard: {
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication completed with an unrecognized result."));
|
||||
oauth2.grant();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,9 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
#include <katabasis/DeviceFlow.h>
|
||||
|
||||
#include <QtNetworkAuth/qoauth2authorizationcodeflow.h>
|
||||
class MSAStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -54,11 +52,8 @@ class MSAStep : public AuthStep {
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onOAuthActivityChanged(Katabasis::Activity activity);
|
||||
|
||||
private:
|
||||
Katabasis::DeviceFlow* m_oauth2 = nullptr;
|
||||
Action m_action;
|
||||
QString m_clientId;
|
||||
QOAuth2AuthorizationCodeFlow oauth2;
|
||||
};
|
||||
|
||||
@@ -171,9 +171,9 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error)
|
||||
}
|
||||
}
|
||||
// error happened during download.
|
||||
qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
||||
qCCritical(logCat) << getUid().toString() << "Failed" << m_url.toString() << "with reason" << error;
|
||||
if (m_reply)
|
||||
qCCritical(logCat) << getUid().toString() << "HTTP Status " << replyStatusCode() << ";error " << errorString();
|
||||
qCCritical(logCat) << getUid().toString() << "HTTP Status" << replyStatusCode() << ";error" << errorString();
|
||||
m_state = State::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
qt.*.debug=false
|
||||
# don't log credentials by default
|
||||
launcher.auth.credentials.debug=false
|
||||
katabasis.*.debug=false
|
||||
# remove the debug lines, other log levels still get through
|
||||
launcher.task.net.download.debug=false
|
||||
# enable or disable whole catageries
|
||||
|
||||
@@ -67,9 +67,6 @@ int MSALoginDialog::exec()
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
||||
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
||||
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
|
||||
connect(m_loginTask.get(), &AuthFlow::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
|
||||
connect(m_loginTask.get(), &AuthFlow::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
|
||||
connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
||||
m_loginTask->start();
|
||||
|
||||
return QDialog::exec();
|
||||
@@ -80,55 +77,6 @@ MSALoginDialog::~MSALoginDialog()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MSALoginDialog::externalLoginTick()
|
||||
{
|
||||
m_externalLoginElapsed++;
|
||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
||||
ui->progressBar->repaint();
|
||||
|
||||
if (m_externalLoginElapsed >= m_externalLoginTimeout) {
|
||||
m_externalLoginTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn)
|
||||
{
|
||||
m_externalLoginElapsed = 0;
|
||||
m_externalLoginTimeout = expiresIn;
|
||||
|
||||
m_externalLoginTimer.setInterval(1000);
|
||||
m_externalLoginTimer.setSingleShot(false);
|
||||
m_externalLoginTimer.start();
|
||||
|
||||
ui->progressBar->setMaximum(expiresIn);
|
||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
||||
|
||||
QString urlString = uri.toString();
|
||||
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
|
||||
if (urlString == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
||||
urlString += QString("?otc=%1").arg(code);
|
||||
DesktopServices::openUrl(urlString);
|
||||
ui->label->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
||||
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
||||
.arg(linkString, code));
|
||||
} else {
|
||||
ui->label->setText(
|
||||
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
||||
}
|
||||
ui->actionButton->setVisible(true);
|
||||
connect(ui->actionButton, &QPushButton::clicked, [=]() {
|
||||
DesktopServices::openUrl(uri);
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(code);
|
||||
});
|
||||
}
|
||||
|
||||
void MSALoginDialog::hideVerificationUriAndCode()
|
||||
{
|
||||
m_externalLoginTimer.stop();
|
||||
ui->actionButton->setVisible(false);
|
||||
}
|
||||
|
||||
void MSALoginDialog::setUserInputsEnabled(bool enable)
|
||||
{
|
||||
ui->buttonBox->setEnabled(enable);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTimer>
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
@@ -45,16 +44,9 @@ class MSALoginDialog : public QDialog {
|
||||
void onTaskSucceeded();
|
||||
void onTaskStatus(const QString& status);
|
||||
void onTaskProgress(qint64 current, qint64 total);
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
void externalLoginTick();
|
||||
|
||||
private:
|
||||
Ui::MSALoginDialog* ui;
|
||||
MinecraftAccountPtr m_account;
|
||||
shared_qobject_ptr<AuthFlow> m_loginTask;
|
||||
QTimer m_externalLoginTimer;
|
||||
int m_externalLoginElapsed = 0;
|
||||
int m_externalLoginTimeout = 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user