diff --git a/launcher/dialogs/MSALoginDialog.cpp b/launcher/dialogs/MSALoginDialog.cpp
index 14a0e243..989a4c9f 100644
--- a/launcher/dialogs/MSALoginDialog.cpp
+++ b/launcher/dialogs/MSALoginDialog.cpp
@@ -41,6 +41,9 @@ 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(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
+ connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
+ connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
m_loginTask->start();
return QDialog::exec();
@@ -52,6 +55,37 @@ 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("%2").arg(urlString, urlString);
+ ui->label->setText(tr("
Please open up %1 in a browser and put in the code %2 to proceed with login.
").arg(linkString, code));
+}
+
+void MSALoginDialog::hideVerificationUriAndCode() {
+ m_externalLoginTimer.stop();
+}
+
void MSALoginDialog::setUserInputsEnabled(bool enable)
{
ui->buttonBox->setEnabled(enable);
diff --git a/launcher/dialogs/MSALoginDialog.h b/launcher/dialogs/MSALoginDialog.h
index 402180ee..3d26a0dd 100644
--- a/launcher/dialogs/MSALoginDialog.h
+++ b/launcher/dialogs/MSALoginDialog.h
@@ -17,6 +17,7 @@
#include
#include
+#include
#include "minecraft/auth/MinecraftAccount.h"
@@ -46,10 +47,17 @@ slots:
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;
- std::shared_ptr m_loginTask;
+ std::shared_ptr m_loginTask;
+ QTimer m_externalLoginTimer;
+ int m_externalLoginElapsed = 0;
+ int m_externalLoginTimeout = 0;
};
diff --git a/launcher/dialogs/MSALoginDialog.ui b/launcher/dialogs/MSALoginDialog.ui
index 4d82fe2b..78cbfb26 100644
--- a/launcher/dialogs/MSALoginDialog.ui
+++ b/launcher/dialogs/MSALoginDialog.ui
@@ -30,6 +30,9 @@ aaaaa
Qt::RichText
+
+ true
+
Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h
index 3f08096f..fc3488eb 100644
--- a/launcher/minecraft/auth/AccountTask.h
+++ b/launcher/minecraft/auth/AccountTask.h
@@ -83,6 +83,10 @@ public:
return m_accountState;
}
+signals:
+ void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
+ void hideVerificationUriAndCode();
+
protected:
/**
diff --git a/launcher/minecraft/auth/flows/AuthContext.cpp b/launcher/minecraft/auth/flows/AuthContext.cpp
index 9754d1a9..ecd7e310 100644
--- a/launcher/minecraft/auth/flows/AuthContext.cpp
+++ b/launcher/minecraft/auth/flows/AuthContext.cpp
@@ -43,7 +43,7 @@ void AuthContext::finishActivity() {
throw 0;
}
m_activity = Katabasis::Activity::Idle;
- m_stage = MSAStage::Idle;
+ setStage(AuthStage::Complete);
m_data->validity_ = m_data->minecraftProfile.validity;
emit activityChanged(m_activity);
}
@@ -55,16 +55,16 @@ void AuthContext::initMSA() {
Katabasis::OAuth2::Options opts;
opts.scope = "XboxLive.signin offline_access";
opts.clientIdentifier = BuildConfig.MSA_CLIENT_ID;
- opts.authorizationUrl = "https://login.live.com/oauth20_authorize.srf";
- opts.accessTokenUrl = "https://login.live.com/oauth20_token.srf";
+ opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
+ opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
opts.listenerPorts = {28562, 28563, 28564, 28565, 28566};
m_oauth2 = new OAuth2(opts, m_data->msaToken, this, mgr);
+ m_oauth2->setGrantFlow(Katabasis::OAuth2::GrantFlowDevice);
connect(m_oauth2, &OAuth2::linkingFailed, this, &AuthContext::onOAuthLinkingFailed);
connect(m_oauth2, &OAuth2::linkingSucceeded, this, &AuthContext::onOAuthLinkingSucceeded);
- connect(m_oauth2, &OAuth2::openBrowser, this, &AuthContext::onOpenBrowser);
- connect(m_oauth2, &OAuth2::closeBrowser, this, &AuthContext::onCloseBrowser);
+ connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &AuthContext::showVerificationUriAndCode);
connect(m_oauth2, &OAuth2::activityChanged, this, &AuthContext::onOAuthActivityChanged);
}
@@ -106,20 +106,14 @@ bool AuthContext::signOut() {
}
*/
-void AuthContext::onOpenBrowser(const QUrl &url) {
- QDesktopServices::openUrl(url);
-}
-
-void AuthContext::onCloseBrowser() {
-
-}
-
void AuthContext::onOAuthLinkingFailed() {
+ emit hideVerificationUriAndCode();
finishActivity();
changeState(STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
}
void AuthContext::onOAuthLinkingSucceeded() {
+ emit hideVerificationUriAndCode();
auto *o2t = qobject_cast(sender());
if (!o2t->linked()) {
finishActivity();
@@ -143,7 +137,7 @@ void AuthContext::onOAuthActivityChanged(Katabasis::Activity activity) {
}
void AuthContext::doUserAuth() {
- m_stage = MSAStage::UserAuth;
+ setStage(AuthStage::UserAuth);
changeState(STATE_WORKING, tr("Starting user authentication"));
QString xbox_auth_template = R"XXX(
@@ -307,7 +301,7 @@ void AuthContext::onUserAuthDone(
}
m_data->userToken = temp;
- m_stage = MSAStage::XboxAuth;
+ setStage(AuthStage::XboxAuth);
changeState(STATE_WORKING, tr("Starting XBox authentication"));
doSTSAuthMinecraft();
@@ -671,7 +665,7 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
}
void AuthContext::doMinecraftProfile() {
- m_stage = MSAStage::MinecraftProfile;
+ setStage(AuthStage::MinecraftProfile);
changeState(STATE_WORKING, tr("Starting minecraft profile acquisition"));
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
@@ -710,7 +704,7 @@ void AuthContext::onMinecraftProfileDone(int, QNetworkReply::NetworkError error,
}
void AuthContext::doGetSkin() {
- m_stage = MSAStage::Skin;
+ setStage(AuthStage::Skin);
changeState(STATE_WORKING, tr("Fetching player skin"));
auto url = QUrl(m_data->minecraftProfile.skin.url);
@@ -730,12 +724,18 @@ void AuthContext::onSkinDone(int, QNetworkReply::NetworkError error, QByteArray
changeState(STATE_SUCCEEDED, tr("Finished all authentication steps"));
}
+void AuthContext::setStage(AuthContext::AuthStage stage) {
+ m_stage = stage;
+ emit progress((int)m_stage, (int)AuthStage::Complete);
+}
+
+
QString AuthContext::getStateMessage() const {
switch (m_accountState)
{
case STATE_WORKING:
switch(m_stage) {
- case MSAStage::Idle: {
+ case AuthStage::Initial: {
QString loginMessage = tr("Logging in as %1 user");
if(m_data->type == AccountType::MSA) {
return loginMessage.arg("Microsoft");
@@ -744,14 +744,16 @@ QString AuthContext::getStateMessage() const {
return loginMessage.arg("Mojang");
}
}
- case MSAStage::UserAuth:
+ case AuthStage::UserAuth:
return tr("Logging in as XBox user");
- case MSAStage::XboxAuth:
+ case AuthStage::XboxAuth:
return tr("Logging in with XBox and Mojang services");
- case MSAStage::MinecraftProfile:
+ case AuthStage::MinecraftProfile:
return tr("Getting Minecraft profile");
- case MSAStage::Skin:
+ case AuthStage::Skin:
return tr("Getting Minecraft skin");
+ case AuthStage::Complete:
+ return tr("Finished");
default:
break;
}
diff --git a/launcher/minecraft/auth/flows/AuthContext.h b/launcher/minecraft/auth/flows/AuthContext.h
index 5f99dba3..1d9f8f72 100644
--- a/launcher/minecraft/auth/flows/AuthContext.h
+++ b/launcher/minecraft/auth/flows/AuthContext.h
@@ -36,8 +36,7 @@ private slots:
// OAuth-specific callbacks
void onOAuthLinkingSucceeded();
void onOAuthLinkingFailed();
- void onOpenBrowser(const QUrl &url);
- void onCloseBrowser();
+
void onOAuthActivityChanged(Katabasis::Activity activity);
// Yggdrasil specific callbacks
@@ -82,13 +81,16 @@ protected:
bool m_xboxProfileSucceeded = false;
bool m_mcAuthSucceeded = false;
Katabasis::Activity m_activity = Katabasis::Activity::Idle;
- enum class MSAStage {
- Idle,
+ enum class AuthStage {
+ Initial,
UserAuth,
XboxAuth,
MinecraftProfile,
- Skin
- } m_stage = MSAStage::Idle;
+ Skin,
+ Complete
+ } m_stage = AuthStage::Initial;
+
+ void setStage(AuthStage stage);
QNetworkAccessManager *mgr = nullptr;
};
diff --git a/libraries/katabasis/include/katabasis/OAuth2.h b/libraries/katabasis/include/katabasis/OAuth2.h
index 4361691c..9dbe5c71 100644
--- a/libraries/katabasis/include/katabasis/OAuth2.h
+++ b/libraries/katabasis/include/katabasis/OAuth2.h
@@ -140,7 +140,7 @@ signals:
void closeBrowser();
/// Emitted when client needs to show a verification uri and user code
- void showVerificationUriAndCode(const QUrl &uri, const QString &code);
+ void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
/// Emitted when authentication/deauthentication succeeded.
void linkingSucceeded();
@@ -181,7 +181,7 @@ protected:
void setExpires(QDateTime v);
/// Start polling authorization server
- void startPollServer(const QVariantMap ¶ms);
+ void startPollServer(const QVariantMap ¶ms, int expiresIn);
/// Set authentication token.
void setToken(const QString &v);
diff --git a/libraries/katabasis/src/OAuth2.cpp b/libraries/katabasis/src/OAuth2.cpp
index 3c07420c..9756d377 100644
--- a/libraries/katabasis/src/OAuth2.cpp
+++ b/libraries/katabasis/src/OAuth2.cpp
@@ -472,16 +472,8 @@ void OAuth2::setExpires(QDateTime v) {
token_.notAfter = v;
}
-void OAuth2::startPollServer(const QVariantMap ¶ms)
+void OAuth2::startPollServer(const QVariantMap ¶ms, int expiresIn)
{
- bool ok = false;
- int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok);
- if (!ok) {
- qWarning() << "OAuth2::startPollServer: No expired_in parameter";
- emit linkingFailed();
- return;
- }
-
qDebug() << "OAuth2::startPollServer: device_ and user_code expires in" << expiresIn << "seconds";
QUrl url(options_.accessTokenUrl);
@@ -502,6 +494,7 @@ void OAuth2::startPollServer(const QVariantMap ¶ms)
PollServer * pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this);
if (params.contains(OAUTH2_INTERVAL)) {
+ bool ok = false;
int interval = params[OAUTH2_INTERVAL].toInt(&ok);
if (ok)
pollServer->setInterval(interval);
@@ -629,9 +622,17 @@ void OAuth2::onDeviceAuthReplyFinished()
if (params.contains(OAUTH2_VERIFICATION_URI_COMPLETE))
emit openBrowser(params.take(OAUTH2_VERIFICATION_URI_COMPLETE).toUrl());
- emit showVerificationUriAndCode(uri, userCode);
+ bool ok = false;
+ int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok);
+ if (!ok) {
+ qWarning() << "OAuth2::startPollServer: No expired_in parameter";
+ emit linkingFailed();
+ return;
+ }
- startPollServer(params);
+ emit showVerificationUriAndCode(uri, userCode, expiresIn);
+
+ startPollServer(params, expiresIn);
} else {
qWarning() << "OAuth2::onDeviceAuthReplyFinished: Mandatory parameters missing from response";
emit linkingFailed();