From 3aa0470510ec9eee0cc3e3bed244a823c20c08fa Mon Sep 17 00:00:00 2001 From: dijunkun Date: Sat, 10 Oct 2026 17:00:58 +0800 Subject: [PATCH] [fix] restore password prompts and handle join authentication failures --- .../src/gui/application/application_state.h | 2 - .../src/gui/application/gui_application.cpp | 45 +++++++----- .../assets/localization/localization_data.h | 4 ++ .../src/gui/runtime/connection_auth_state.h | 72 +++++++++++++++++++ .../src/gui/runtime/connection_runtime.cpp | 19 ++++- .../src/gui/runtime/peer_event_handler.cpp | 14 ++-- apps/desktop/src/gui/runtime/remote_session.h | 2 + .../tests/connection_status_protocol_test.cpp | 4 +- deps/submodules/minirtc | 2 +- 9 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 apps/desktop/src/gui/runtime/connection_auth_state.h diff --git a/apps/desktop/src/gui/application/application_state.h b/apps/desktop/src/gui/application/application_state.h index 2df96342..f8a1116a 100644 --- a/apps/desktop/src/gui/application/application_state.h +++ b/apps/desktop/src/gui/application/application_state.h @@ -72,8 +72,6 @@ struct ServerWindowState { }; struct UiState { - bool password_validating_ = false; - uint32_t password_validating_time_ = 0; bool rejoin_ = false; bool show_password_ = true; bool show_connection_status_window_ = false; diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 07494d23..b8d989db 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -1451,14 +1451,8 @@ void GuiApplication::BindMainCallbacks() { show_connection_status_window_ = false; return; } - const ConnectionStatus status = props->connection_status_.load(); - if (status == ConnectionStatus::IncorrectPassword) { - std::memset(props->remote_password_, 0, sizeof(props->remote_password_)); - password_validating_ = false; - } else { - CloseStreamTab(props->remote_id_); - re_enter_remote_id_ = true; - } + CloseStreamTab(props->remote_id_); + re_enter_remote_id_ = true; show_connection_status_window_ = false; }); main->on_connection_acknowledge([this] { @@ -1480,11 +1474,11 @@ void GuiApplication::BindMainCallbacks() { if (!props || password.empty() || password.size() > 6) { return; } + if (!props->connection_auth_.Begin(password)) return; std::memset(props->remote_password_, 0, sizeof(props->remote_password_)); std::memcpy(props->remote_password_, password.data(), password.size()); props->remember_password_ = remember; props->rejoin_ = true; - password_validating_ = true; need_to_rejoin_ = true; }); main->on_request_screen_recording_permission([this] { @@ -2580,26 +2574,41 @@ void GuiApplication::SyncConnectionDialog() { } ui_->connection_dialog_remote_id = props->remote_id_; const ConnectionStatus status = props->connection_status_.load(); - const bool password_required = status == ConnectionStatus::IncorrectPassword; - const bool pending = status == ConnectionStatus::Connecting || - status == ConnectionStatus::Gathering; + const auto auth = props->connection_auth_.Get(); + const bool password_required = auth.password_required || + (status == ConnectionStatus::IncorrectPassword && auth.validating); + const bool pending = !password_required && + (status == ConnectionStatus::Connecting || status == ConnectionStatus::Gathering); std::string text; if (password_required) { - text = password_validating_ + text = auth.validating ? localization::validate_password[localization_language_index_] - : password_validating_time_ <= 1 - ? localization::input_password[localization_language_index_] - : localization::reinput_password[localization_language_index_]; + : auth.incorrect_password + ? localization::reinput_password[localization_language_index_] + : localization::input_password[localization_language_index_]; + } else if (auth.error == minirtc::JoinFailureKind::Throttled) { + text = localization::connection_auth_throttled[localization_language_index_]; + } else if (auth.error == minirtc::JoinFailureKind::Busy) { + text = localization::connection_service_busy[localization_language_index_]; + } else if (auth.error == minirtc::JoinFailureKind::NotAuthenticated) { + text = localization::connection_not_authenticated[localization_language_index_]; } else { text = ConnectionStatusText(status, localization_language_index_); } + if (auth.retry_after > 0) { + char retry_text[256]; + std::snprintf(retry_text, sizeof(retry_text), + localization::connection_retry_after[localization_language_index_].c_str(), + auth.retry_after); + text += "\n" + std::string(retry_text); + } ui_->main->set_connection_dialog_open(true); ui_->main->set_connection_status_text(UiText(text)); ui_->main->set_connection_pending(pending); ui_->main->set_connection_password_required(password_required); - ui_->main->set_connection_validating(password_validating_); - if (password_required && !password_validating_ && + ui_->main->set_connection_validating(auth.validating); + if (password_required && !auth.validating && !ui_->connection_password_initialized) { ui_->main->set_connection_password(props->remote_password_); ui_->main->set_connection_remember_password(props->remember_password_); diff --git a/apps/desktop/src/gui/assets/localization/localization_data.h b/apps/desktop/src/gui/assets/localization/localization_data.h index 08a8f9fc..e923cc5b 100644 --- a/apps/desktop/src/gui/assets/localization/localization_data.h +++ b/apps/desktop/src/gui/assets/localization/localization_data.h @@ -272,6 +272,10 @@ struct TranslationRow { X(receiving_screen, u8"画面接收中...", "Receiving screen...", \ u8"Получение изображения...") \ X(p2p_failed, u8"对等连接失败", "P2P Failed", u8"Сбой P2P") \ + X(connection_auth_throttled, u8"验证尝试过于频繁,请稍后重试。", "Too many authentication attempts. Try again later.", u8"Слишком много попыток. Повторите позже.") \ + X(connection_service_busy, u8"认证服务繁忙,请稍后重试。", "Authentication service busy. Try again later.", u8"Сервис проверки занят. Повторите позже.") \ + X(connection_not_authenticated, u8"登录状态已失效,请重新连接。", "Signaling login expired. Please reconnect.", u8"Сеанс входа истёк. Подключитесь заново.") \ + X(connection_retry_after, u8"请在 %d 秒后重试。", "Retry in %d seconds.", u8"Повторите через %d сек.") \ X(p2p_closed, u8"对等连接已关闭", "P2P closed", u8"P2P закрыто") \ X(no_such_id, u8"无此ID", "No such ID", u8"ID не найден") \ X(about, u8"关于", "About", u8"О программе") \ diff --git a/apps/desktop/src/gui/runtime/connection_auth_state.h b/apps/desktop/src/gui/runtime/connection_auth_state.h new file mode 100644 index 00000000..e87e1e41 --- /dev/null +++ b/apps/desktop/src/gui/runtime/connection_auth_state.h @@ -0,0 +1,72 @@ +#ifndef CROSSDESK_CONNECTION_AUTH_STATE_H_ +#define CROSSDESK_CONNECTION_AUTH_STATE_H_ + +#include +#include +#include + +#include "join_failure.h" + +namespace crossdesk { + +// One remote session's password prompt and server-requested retry delay. +// Signaling callbacks and the UI access a single synchronized snapshot. +class ConnectionAuthState { + public: + using Clock = std::chrono::steady_clock; + struct Snapshot { + bool password_required = false; + bool validating = false; + bool incorrect_password = false; + minirtc::JoinFailureKind error = minirtc::JoinFailureKind::Unknown; + int retry_after = 0; + }; + + bool Begin(std::string_view password, Clock::time_point now = Clock::now()) { + std::lock_guard lock(mutex_); + if (retry_at_ > now) return false; + state_ = {}; + retry_at_ = {}; + state_.password_required = password.empty(); + state_.validating = !password.empty(); + return state_.validating; + } + + void Rejected(const nlohmann::json& reply, + Clock::time_point now = Clock::now()) { + const auto failure = minirtc::ParseJoinFailure(reply); + std::lock_guard lock(mutex_); + state_ = {}; + state_.error = failure.kind; + state_.incorrect_password = + failure.kind == minirtc::JoinFailureKind::AuthenticationFailed; + state_.password_required = + state_.incorrect_password || + failure.kind == minirtc::JoinFailureKind::PasswordRequired; + retry_at_ = now + std::chrono::seconds(failure.retry_after); + } + + void Complete() { + std::lock_guard lock(mutex_); + state_.validating = false; + } + + Snapshot Get(Clock::time_point now = Clock::now()) const { + std::lock_guard lock(mutex_); + auto snapshot = state_; + if (retry_at_ > now) { + snapshot.retry_after = static_cast( + std::chrono::ceil(retry_at_ - now).count()); + } + return snapshot; + } + + private: + mutable std::mutex mutex_; + Snapshot state_; + Clock::time_point retry_at_{}; +}; + +} // namespace crossdesk + +#endif diff --git a/apps/desktop/src/gui/runtime/connection_runtime.cpp b/apps/desktop/src/gui/runtime/connection_runtime.cpp index 85b6d172..3859f2f3 100644 --- a/apps/desktop/src/gui/runtime/connection_runtime.cpp +++ b/apps/desktop/src/gui/runtime/connection_runtime.cpp @@ -479,6 +479,14 @@ std::shared_ptr GuiRuntime::FindRemoteSession( int GuiRuntime::ConnectTo(const std::string& remote_id, const char* password, bool remember_password, bool bypass_presence_check) { + // Do not recreate a failed peer or send another probe during a server cooldown. + if (const auto current = FindRemoteSession(remote_id); + current && current->connection_auth_.Get().retry_after > 0) { + focused_remote_id_ = remote_id; + show_connection_status_window_ = true; + current->rejoin_ = false; + return 0; + } if (session_cleanup_tasks_.count(remote_id)) { pending_reconnects_[remote_id] = {password ? password : "", remember_password}; LOG_INFO("[{}] Reconnect queued until previous peer is destroyed", remote_id); @@ -595,18 +603,23 @@ int GuiRuntime::ConnectTo(const std::string& remote_id, const char* password, auto props = FindRemoteSession(remote_id); if (!props || props->closing_) return -1; if (!props->connection_established_) { - props->connection_status_.store(ConnectionStatus::Connecting); show_connection_status_window_ = true; props->remember_password_ = remember_password; - if (strcmp(password, "") != 0 && + if (password && *password && strcmp(password, props->remote_password_) != 0) { strncpy(props->remote_password_, password, sizeof(props->remote_password_) - 1); props->remote_password_[sizeof(props->remote_password_) - 1] = '\0'; } - std::string remote_id_with_pwd = remote_id + "@" + password; + // Ask locally before Join; an empty-password round trip is no longer needed. + if (!props->connection_auth_.Begin(props->remote_password_)) { + props->rejoin_ = false; + return 0; + } + props->connection_status_.store(ConnectionStatus::Connecting); + std::string remote_id_with_pwd = remote_id + "@" + props->remote_password_; if (props->peer_) { ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str()); if (0 == ret) { diff --git a/apps/desktop/src/gui/runtime/peer_event_handler.cpp b/apps/desktop/src/gui/runtime/peer_event_handler.cpp index 7b9fd044..1e8a75a9 100644 --- a/apps/desktop/src/gui/runtime/peer_event_handler.cpp +++ b/apps/desktop/src/gui/runtime/peer_event_handler.cpp @@ -79,7 +79,12 @@ void PeerEventHandler::OnSignalMessage(const char* message, size_t size, return; } std::string type = j["type"].get(); - if (type == "announcements_changed") { + if (type == "user_join_transmission" && j.contains("status") && + j["status"] == "failed" && j.contains("transmission_id") && + j["transmission_id"].is_string()) { + const auto props = runtime->FindRemoteSession(j["transmission_id"].get()); + if (props && !props->closing_) props->connection_auth_.Rejected(j); + } else if (type == "announcements_changed") { runtime->announcements_.Refresh(); } else if (type == "announcements") { runtime->announcements_.Receive(j); @@ -265,6 +270,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, runtime->is_client_mode_ = true; runtime->show_connection_status_window_ = true; props->connection_status_.store(status); + if (status != ConnectionStatus::Connecting && status != ConnectionStatus::Gathering) + props->connection_auth_.Complete(); switch (status) { case ConnectionStatus::Connected: { @@ -385,11 +392,6 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, break; } - case ConnectionStatus::IncorrectPassword: { - runtime->password_validating_ = false; - runtime->password_validating_time_++; - break; - } default: break; } diff --git a/apps/desktop/src/gui/runtime/remote_session.h b/apps/desktop/src/gui/runtime/remote_session.h index f9bccb84..73ebe66f 100644 --- a/apps/desktop/src/gui/runtime/remote_session.h +++ b/apps/desktop/src/gui/runtime/remote_session.h @@ -26,6 +26,7 @@ #include "minirtc.h" #include "rendering/video_latency.h" #include "runtime/net_traffic_stats_cache.h" +#include "runtime/connection_auth_state.h" namespace crossdesk { class PeerEventHandler; } @@ -100,6 +101,7 @@ struct RemoteSession { bool p2p_mode_ = true; bool remember_password_ = false; char remote_password_[7] = ""; + ConnectionAuthState connection_auth_; // Reused across frames by the decode callback thread when a CPU NV12 // native frame has to be materialized for a renderer that rejects it. std::vector native_cpu_fallback_; diff --git a/apps/desktop/tests/connection_status_protocol_test.cpp b/apps/desktop/tests/connection_status_protocol_test.cpp index 03ad5051..774dab9a 100644 --- a/apps/desktop/tests/connection_status_protocol_test.cpp +++ b/apps/desktop/tests/connection_status_protocol_test.cpp @@ -83,9 +83,7 @@ int main() { ok &= ExpectContains("minirtc.h", minirtc_api, "RemoteUnavailable"); ok &= ExpectNotContains("minirtc.h", minirtc_api, "DeviceOffline"); ok &= ExpectContains("peer_connection.cpp", peer_connection, - "\"Remote unavailable\""); - ok &= ExpectContains("peer_connection.cpp", peer_connection, - "ConnectionStatus::RemoteUnavailable"); + "on_connection_status_(ParseJoinFailure(j).status,"); ok &= ExpectContains("peer_connection.cpp", peer_connection, "IsCurrentPeerConnection"); ok &= ExpectContains( diff --git a/deps/submodules/minirtc b/deps/submodules/minirtc index a017ce97..c44807de 160000 --- a/deps/submodules/minirtc +++ b/deps/submodules/minirtc @@ -1 +1 @@ -Subproject commit a017ce97f2756731b322fc120d6f2fa225a84b6f +Subproject commit c44807de578d3ec7f21b264ef9c8937ff34f4329