From caf1f93131ae5b729bfb32c446a3dd2f609f6c5b Mon Sep 17 00:00:00 2001 From: Zephyron Date: Tue, 21 Oct 2025 18:34:18 +1000 Subject: [PATCH] feat: auto-generate multiplayer tokens Replace manual token verification with automatic UUID generation. Tokens are now auto-generated on first save and can be reset via button. - Remove verification logic and base64 encoding - Add ResetToken() method with UUID generation - Sync profile username to web service settings - Simplify UI and improve error messages Based on Torzu PRs #22 and #28. Co-authored-by: anon Co-authored-by: spectranator Signed-off-by: Zephyron --- .../configure_profile_manager.cpp | 5 + src/citron/configuration/configure_web.cpp | 123 ++++-------------- src/citron/configuration/configure_web.h | 9 +- src/citron/configuration/configure_web.ui | 17 ++- src/citron/multiplayer/host_room.cpp | 9 +- 5 files changed, 46 insertions(+), 117 deletions(-) diff --git a/src/citron/configuration/configure_profile_manager.cpp b/src/citron/configuration/configure_profile_manager.cpp index 82d3b4c94..b6e528730 100644 --- a/src/citron/configuration/configure_profile_manager.cpp +++ b/src/citron/configuration/configure_profile_manager.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -177,6 +178,10 @@ void ConfigureProfileManager::UpdateCurrentUser() { scene->addPixmap( GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); ui->current_user_username->setText(username); + + // Update the token username for web service configuration + // This will be processed by ConfigureWeb::ApplyConfiguration() + Settings::values.citron_username = username.toStdString(); } void ConfigureProfileManager::ApplyConfiguration() { diff --git a/src/citron/configuration/configure_web.cpp b/src/citron/configuration/configure_web.cpp index d137c3f7d..b3e54c400 100644 --- a/src/citron/configuration/configure_web.cpp +++ b/src/citron/configuration/configure_web.cpp @@ -1,47 +1,22 @@ // SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include -#include #include "common/settings.h" +#include "common/uuid.h" #include "core/telemetry_session.h" #include "ui_configure_web.h" #include "citron/configuration/configure_web.h" #include "citron/uisettings.h" -static constexpr char token_delimiter{':'}; - -static std::string GenerateDisplayToken(const std::string& username, const std::string& token) { - if (username.empty() || token.empty()) { - return {}; - } - - const std::string unencoded_display_token{username + token_delimiter + token}; - QByteArray b{unencoded_display_token.c_str()}; - QByteArray b64 = b.toBase64(); - return b64.toStdString(); -} - -static std::string UsernameFromDisplayToken(const std::string& display_token) { - const std::string unencoded_display_token{ - QByteArray::fromBase64(display_token.c_str()).toStdString()}; - return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter)); -} - -static std::string TokenFromDisplayToken(const std::string& display_token) { - const std::string unencoded_display_token{ - QByteArray::fromBase64(display_token.c_str()).toStdString()}; - return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1); -} - ConfigureWeb::ConfigureWeb(QWidget* parent) : QWidget(parent), ui(std::make_unique()) { ui->setupUi(this); connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this, &ConfigureWeb::RefreshTelemetryID); - connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin); - connect(&verify_watcher, &QFutureWatcher::finished, this, &ConfigureWeb::OnLoginVerified); + connect(ui->button_reset_token, &QPushButton::clicked, this, &ConfigureWeb::ResetToken); #ifndef USE_DISCORD_PRESENCE ui->discord_group->setVisible(false); @@ -68,24 +43,13 @@ void ConfigureWeb::RetranslateUI() { tr("Learn more")); - ui->web_signup_link->setText( - tr("Sign up")); - - ui->web_token_info_link->setText( - tr("Get Support")); - ui->label_telemetry_id->setText( tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); } void ConfigureWeb::SetConfiguration() { ui->web_credentials_disclaimer->setWordWrap(true); - ui->telemetry_learn_more->setOpenExternalLinks(true); - ui->web_signup_link->setOpenExternalLinks(true); - ui->web_token_info_link->setOpenExternalLinks(true); if (Settings::values.citron_username.GetValue().empty()) { ui->username->setText(tr("Unspecified")); @@ -94,28 +58,25 @@ void ConfigureWeb::SetConfiguration() { } ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry.GetValue()); - ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken( - Settings::values.citron_username.GetValue(), Settings::values.citron_token.GetValue()))); - - // Connect after setting the values, to avoid calling OnLoginChanged now - connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); - - user_verified = true; - + ui->edit_token->setText(QString::fromStdString(Settings::values.citron_token.GetValue())); ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue()); } void ConfigureWeb::ApplyConfiguration() { Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked(); UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); - if (user_verified) { - Settings::values.citron_username = - UsernameFromDisplayToken(ui->edit_token->text().toStdString()); - Settings::values.citron_token = TokenFromDisplayToken(ui->edit_token->text().toStdString()); + + // Username is set from the profile manager via UpdateCurrentUser() + // Use a default value if username is still empty + if (Settings::values.citron_username.GetValue().empty()) { + Settings::values.citron_username = "citron"; + } + + // Auto-generate token if empty, otherwise use the user-provided value + if (ui->edit_token->text().isEmpty()) { + Settings::values.citron_token = Common::UUID::MakeRandom().FormattedString(); } else { - QMessageBox::warning( - this, tr("Token not verified"), - tr("Token was not verified. The change to your token has not been saved.")); + Settings::values.citron_token = ui->edit_token->text().toStdString(); } } @@ -125,53 +86,17 @@ void ConfigureWeb::RefreshTelemetryID() { tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); } -void ConfigureWeb::OnLoginChanged() { - if (ui->edit_token->text().isEmpty()) { - user_verified = true; - // Empty = no icon - ui->label_token_verified->setPixmap(QPixmap()); - ui->label_token_verified->setToolTip(QString()); - } else { - user_verified = false; +void ConfigureWeb::ResetToken() { + // Generate a new random token + const auto new_token = Common::UUID::MakeRandom().FormattedString(); + Settings::values.citron_token = new_token; - // Show an info icon if it's been changed, clearer than showing failure - const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16); - ui->label_token_verified->setPixmap(pixmap); - ui->label_token_verified->setToolTip( - tr("Unverified, please click Verify before saving configuration", "Tooltip")); - } -} + // Update the UI to show the new token + ui->edit_token->setText(QString::fromStdString(new_token)); -void ConfigureWeb::VerifyLogin() { - ui->button_verify_login->setDisabled(true); - ui->button_verify_login->setText(tr("Verifying...")); - ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16)); - ui->label_token_verified->setToolTip(tr("Verifying...")); - verify_watcher.setFuture(QtConcurrent::run( - [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), - token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { - return Core::VerifyLogin(username, token); - })); -} - -void ConfigureWeb::OnLoginVerified() { - ui->button_verify_login->setEnabled(true); - ui->button_verify_login->setText(tr("Verify")); - if (verify_watcher.result()) { - user_verified = true; - - ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16)); - ui->label_token_verified->setToolTip(tr("Verified", "Tooltip")); - ui->username->setText( - QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); - } else { - ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16)); - ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip")); - ui->username->setText(tr("Unspecified")); - QMessageBox::critical(this, tr("Verification failed"), - tr("Verification failed. Check that you have entered your token " - "correctly, and that your internet connection is working.")); - } + // Show visual confirmation + ui->label_token_icon->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16)); + ui->label_token_icon->setToolTip(tr("Token reset successfully", "Tooltip")); } void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) { diff --git a/src/citron/configuration/configure_web.h b/src/citron/configuration/configure_web.h index 03feb55f8..02839a508 100644 --- a/src/citron/configuration/configure_web.h +++ b/src/citron/configuration/configure_web.h @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include #include namespace Ui { @@ -26,14 +26,9 @@ private: void RetranslateUI(); void RefreshTelemetryID(); - void OnLoginChanged(); - void VerifyLogin(); - void OnLoginVerified(); + void ResetToken(); void SetConfiguration(); - bool user_verified = true; - QFutureWatcher verify_watcher; - std::unique_ptr ui; }; diff --git a/src/citron/configuration/configure_web.ui b/src/citron/configuration/configure_web.ui index 906fa77ad..6a71ba3f7 100644 --- a/src/citron/configuration/configure_web.ui +++ b/src/citron/configuration/configure_web.ui @@ -28,14 +28,14 @@ - By providing your username and token, you agree to allow citron to collect additional usage data, which may include user identifying information. + This token is for hosting public rooms in a lobby and is automatically generated the first time you save settings. To change your username, rename or switch the profile. If you are currently connected to multiplayer, exit and restart the emulator for changes to apply. - + 0 @@ -46,12 +46,15 @@ Qt::RightToLeft - Verify + Reset Token + + false + Sign up @@ -68,7 +71,7 @@ - + @@ -82,13 +85,13 @@ 80 - - QLineEdit::Password - + + false + What is my token? diff --git a/src/citron/multiplayer/host_room.cpp b/src/citron/multiplayer/host_room.cpp index 596fb94f3..5852146bc 100644 --- a/src/citron/multiplayer/host_room.cpp +++ b/src/citron/multiplayer/host_room.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -183,10 +184,10 @@ void HostRoomWindow::Host() { if (result.result_code != WebService::WebResult::Code::Success) { QMessageBox::warning( this, tr("Error"), - tr("Failed to announce the room to the public lobby. In order to host a " - "room publicly, you must have a valid citron account configured in " - "Emulation -> Configure -> Web. If you do not want to publish a room in " - "the public lobby, then select Unlisted instead.\nDebug Message: ") + + tr("Failed to announce the room to the public lobby. To host a room " + "publicly, you must have a generated token configured in " + "Emulation -> Configure -> Web. If you do not want to publish a room " + "in a public lobby, then select Unlisted instead.\n\nDebug Message: ") + QString::fromStdString(result.result_string), QMessageBox::Ok); ui->host->setEnabled(true);