ShatteredPrism/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp
Evan Goode 9d442a0348 authlib-injector
* 'Custom Yggdrasil' AccountType

Signed-off-by: Evan Goode <mail@evangoo.de>

* Allow multiple accounts with the same player UUID

Signed-off-by: Evan Goode <mail@evangoo.de>

* Use correct services server URL for SkinDelete

Signed-off-by: Evan Goode <mail@evangoo.de>

* Correctly use CustomYggdrasilRefresh, add warning message

Signed-off-by: Evan Goode <mail@evangoo.de>

* Custom Yggdrasil: Readability fixes

Also made the MinecraftEntitlement for Custom Yggdrasil accounts work
just like offline accounts---instead of checking the reply from the auth
server, Custom Yggdrasil accounts are granted canPlayMinecraft and
ownsMinecraft when they are created, in MinecraftAccount.cpp.

Signed-off-by: Evan Goode <mail@evangoo.de>

* Custom Yggdrasil: Add extra confirmation dialog

Signed-off-by: Evan Goode <mail@evangoo.de>

* Add install authlib-injector button

Signed-off-by: Evan Goode <mail@evangoo.de>

* authlib-injector accounts

Signed-off-by: Evan Goode <mail@evangoo.de>

* Suggest installing authlib-injector when needed

Signed-off-by: Evan Goode <mail@evangoo.de>

* Use Unmojang metadata

Signed-off-by: Evan Goode <mail@evangoo.de>

* Use std::string for MANAGED_AGENTS, not QString

---------

Signed-off-by: Evan Goode <mail@evangoo.de>

Resolve X-Authlib-Injector-API-Location

Signed-off-by: Evan Goode <mail@evangoo.de>

Prefetch authlib-injector metadata

Resolves https://github.com/unmojang/PrismLauncher/issues/4

See https://github-com.translate.goog/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en-US#%E9%85%8D%E7%BD%AE%E9%A2%84%E8%8E%B7%E5%8F%96

Signed-off-by: Evan Goode <mail@evangoo.de>

drag-and-drop authlib-injector URL, clang-format

Resolves https://github.com/unmojang/PrismLauncher/issues/2

Signed-off-by: Evan Goode <mail@evangoo.de>
2023-09-04 19:00:18 +01:00

201 lines
7.4 KiB
C++

// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Evan Goode <mail@evangoo.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "AuthlibInjectorLoginDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_AuthlibInjectorLoginDialog.h"
#include "CreateAuthlibInjectorAccount.h"
#include "minecraft/auth/AccountTask.h"
#include <QtWidgets/QPushButton>
AuthlibInjectorLoginDialog::AuthlibInjectorLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AuthlibInjectorLoginDialog)
{
ui->setupUi(this);
ui->progressBar->setVisible(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
setAcceptDrops(true);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
AuthlibInjectorLoginDialog::~AuthlibInjectorLoginDialog()
{
delete ui;
}
QString AuthlibInjectorLoginDialog::fixUrl(QString url)
{
QString fixed(url);
if (!fixed.contains("://")) {
fixed.prepend("https://");
}
if (fixed.endsWith("/")) {
fixed = fixed.left(fixed.size() - 1);
}
return fixed;
}
QUrl AuthlibInjectorLoginDialog::extractAuthlibInjectorUrl(const QMimeData* mimeData)
{
if (mimeData->hasText()) {
const auto& text = mimeData->text().toStdString();
const std::string authlib_injector_uri_prefix{ "authlib-injector:yggdrasil-server:" };
if (text.rfind(authlib_injector_uri_prefix, 0) == 0) {
const auto& url_str = text.substr(authlib_injector_uri_prefix.length());
const auto& url = QUrl(QUrl::fromPercentEncoding(url_str.c_str()));
if (url.isValid()) {
return url;
}
}
}
return {};
}
void AuthlibInjectorLoginDialog::dragEnterEvent(QDragEnterEvent* event)
{
if (extractAuthlibInjectorUrl(event->mimeData()).isValid()) {
event->acceptProposedAction();
}
}
void AuthlibInjectorLoginDialog::dropEvent(QDropEvent* event)
{
const auto& url = extractAuthlibInjectorUrl(event->mimeData());
if (url.isValid()) {
ui->authlibInjectorTextBox->setText(url.toString());
}
event->acceptProposedAction();
}
// Stage 1: User interaction
void AuthlibInjectorLoginDialog::accept()
{
auto fixedAuthlibInjectorUrl = AuthlibInjectorLoginDialog::fixUrl(ui->authlibInjectorTextBox->text());
auto response = CustomMessageBox::selectable(this, QObject::tr("Confirm account creation"),
QObject::tr("Warning: you are about to send the username and password you entered to an "
"unofficial, third-party authentication server:\n"
"%1\n\n"
"Never use your Mojang or Microsoft password for a third-party account!\n\n"
"Are you sure you want to proceed?")
.arg(fixedAuthlibInjectorUrl),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
// Get the authlib-injector API root
auto netJob = NetJob::Ptr(new NetJob("Get authlib-injector API root", APPLICATION->network()));
auto username = ui->userTextBox->text();
m_createAuthlibInjectorAccountTask = CreateAuthlibInjectorAccount::make(fixedAuthlibInjectorUrl, m_account, username);
netJob->addNetAction(m_createAuthlibInjectorAccountTask);
m_createAuthlibInjectorAccountNetJob.reset(netJob);
connect(netJob.get(), &NetJob::succeeded, this, &AuthlibInjectorLoginDialog::onUrlTaskSucceeded);
connect(netJob.get(), &NetJob::failed, this, &AuthlibInjectorLoginDialog::onTaskFailed);
m_createAuthlibInjectorAccountNetJob->start();
}
void AuthlibInjectorLoginDialog::setUserInputsEnabled(bool enable)
{
ui->userTextBox->setEnabled(enable);
ui->passTextBox->setEnabled(enable);
ui->authlibInjectorTextBox->setEnabled(enable);
ui->buttonBox->setEnabled(enable);
}
// Enable the OK button only when all textboxes contain something.
void AuthlibInjectorLoginDialog::on_userTextBox_textEdited(const QString& newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty() && !ui->authlibInjectorTextBox->text().isEmpty());
}
void AuthlibInjectorLoginDialog::on_passTextBox_textEdited(const QString& newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty() && !ui->authlibInjectorTextBox->text().isEmpty());
}
void AuthlibInjectorLoginDialog::on_authlibInjectorTextBox_textEdited(const QString& newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty() && !ui->authlibInjectorTextBox->text().isEmpty());
}
void AuthlibInjectorLoginDialog::onTaskFailed(const QString& reason)
{
// Set message
auto lines = reason.split('\n');
QString processed;
for (auto line : lines) {
if (line.size()) {
processed += "<font color='red'>" + line + "</font><br />";
} else {
processed += "<br />";
}
}
ui->label->setText(processed);
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
}
void AuthlibInjectorLoginDialog::onUrlTaskSucceeded()
{
m_account = m_createAuthlibInjectorAccountTask->getAccount();
m_loginTask = m_account->loginAuthlibInjector(ui->passTextBox->text());
connect(m_loginTask.get(), &Task::failed, this, &AuthlibInjectorLoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &AuthlibInjectorLoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &AuthlibInjectorLoginDialog::onTaskStatus);
connect(m_loginTask.get(), &Task::progress, this, &AuthlibInjectorLoginDialog::onTaskProgress);
m_loginTask->start();
}
void AuthlibInjectorLoginDialog::onTaskSucceeded()
{
QDialog::accept();
}
void AuthlibInjectorLoginDialog::onTaskStatus(const QString& status)
{
ui->label->setText(status);
}
void AuthlibInjectorLoginDialog::onTaskProgress(qint64 current, qint64 total)
{
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(current);
}
// Public interface
MinecraftAccountPtr AuthlibInjectorLoginDialog::newAccount(QWidget* parent, QString msg)
{
AuthlibInjectorLoginDialog dlg(parent);
dlg.ui->label->setText(msg);
if (dlg.exec() == QDialog::Accepted) {
return dlg.m_account;
}
return nullptr;
}