[fix] restore password prompts and handle join authentication failures

This commit is contained in:
dijunkun
2026-10-10 17:00:58 +08:00
parent fbbb8c9e2c
commit 3aa0470510
9 changed files with 131 additions and 33 deletions
@@ -0,0 +1,72 @@
#ifndef CROSSDESK_CONNECTION_AUTH_STATE_H_
#define CROSSDESK_CONNECTION_AUTH_STATE_H_
#include <chrono>
#include <mutex>
#include <string_view>
#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<int>(
std::chrono::ceil<std::chrono::seconds>(retry_at_ - now).count());
}
return snapshot;
}
private:
mutable std::mutex mutex_;
Snapshot state_;
Clock::time_point retry_at_{};
};
} // namespace crossdesk
#endif
@@ -479,6 +479,14 @@ std::shared_ptr<GuiRuntime::RemoteSession> 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) {
@@ -79,7 +79,12 @@ void PeerEventHandler::OnSignalMessage(const char* message, size_t size,
return;
}
std::string type = j["type"].get<std::string>();
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<std::string>());
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;
}
@@ -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<unsigned char> native_cpu_fallback_;