mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-17 09:23:10 +08:00
[feat] add random password refresh to the main window
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include <remote_action.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <slint.h>
|
||||
#include <tinyfiledialogs.h>
|
||||
|
||||
@@ -65,6 +66,28 @@ namespace {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
std::optional<std::string> GenerateRandomPassword(std::string_view current) {
|
||||
constexpr std::string_view alphabet =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
constexpr auto unbiased_limit = 256 - 256 % alphabet.size();
|
||||
std::string password;
|
||||
do {
|
||||
password.clear();
|
||||
while (password.size() < 6) {
|
||||
unsigned char value;
|
||||
if (RAND_bytes(&value, sizeof(value)) != 1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
// Reject the incomplete bucket so every character is equally likely.
|
||||
if (value < unbiased_limit) {
|
||||
password.push_back(alphabet[value % alphabet.size()]);
|
||||
}
|
||||
}
|
||||
} while (password == current ||
|
||||
password.find_first_not_of("0123456789") == std::string::npos);
|
||||
return password;
|
||||
}
|
||||
|
||||
std::string CreatePasswordChangeRequestId(uint64_t sequence) {
|
||||
const auto timestamp = std::chrono::system_clock::now()
|
||||
.time_since_epoch()
|
||||
@@ -1294,78 +1317,17 @@ void GuiApplication::BindMainCallbacks() {
|
||||
ui_->main->set_password_visible(show_password_);
|
||||
});
|
||||
main->on_reset_password([this](slint::SharedString value) {
|
||||
const std::string password(value);
|
||||
if (password.size() != 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!peer_ || !signal_connected_) {
|
||||
offline_warning_text_ =
|
||||
localization::signal_disconnected[localization_language_index_];
|
||||
show_offline_warning_window_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string request_id;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
if (password_change_pending_) {
|
||||
return true;
|
||||
}
|
||||
request_id =
|
||||
CreatePasswordChangeRequestId(++next_password_change_request_id_);
|
||||
password_change_pending_ = true;
|
||||
password_change_result_ready_ = false;
|
||||
password_change_succeeded_ = false;
|
||||
password_change_result_uncertain_ = false;
|
||||
password_change_requested_at_ = std::chrono::steady_clock::now();
|
||||
pending_password_change_request_id_ = request_id;
|
||||
pending_local_password_ = password;
|
||||
password_change_error_.clear();
|
||||
}
|
||||
|
||||
const bool self_hosted = config_center_->IsSelfHosted();
|
||||
const std::string server_host =
|
||||
self_hosted ? config_center_->GetSignalServerHost()
|
||||
: config_center_->GetDefaultServerHost();
|
||||
const int server_port =
|
||||
self_hosted ? config_center_->GetSignalServerPort()
|
||||
: config_center_->GetDefaultSignalServerPort();
|
||||
const std::string pending_identity =
|
||||
std::string(client_id_) + "@" + password;
|
||||
if (!settings_.StagePendingPasswordChange(
|
||||
pending_identity, self_hosted, server_host, server_port,
|
||||
request_id)) {
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
password_change_pending_ = false;
|
||||
pending_password_change_request_id_.clear();
|
||||
pending_local_password_.clear();
|
||||
password_change_error_.clear();
|
||||
return RequestPasswordChange(std::string(value));
|
||||
});
|
||||
main->on_refresh_password([this] {
|
||||
const auto password = GenerateRandomPassword(password_saved_);
|
||||
if (!password) {
|
||||
LOG_ERROR("Could not generate a random password");
|
||||
offline_warning_text_ = localization::failed[localization_language_index_];
|
||||
show_offline_warning_window_ = true;
|
||||
LOG_ERROR("Could not durably stage password change");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
const nlohmann::json request = {{"type", "change_password"},
|
||||
{"request_id", request_id},
|
||||
{"new_password", password}};
|
||||
const std::string message = request.dump();
|
||||
if (SendSignalMessage(peer_, message.data(), message.size()) != 0) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
password_change_pending_ = false;
|
||||
pending_password_change_request_id_.clear();
|
||||
pending_local_password_.clear();
|
||||
offline_warning_text_ =
|
||||
localization::signal_disconnected[localization_language_index_];
|
||||
show_offline_warning_window_ = true;
|
||||
}
|
||||
if (!settings_.ClearPendingPasswordChange()) {
|
||||
LOG_WARN("Could not clear unsent pending password change");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
RequestPasswordChange(*password);
|
||||
});
|
||||
main->on_connect_requested(
|
||||
[this](slint::SharedString id) { ConnectFromUi(std::string(id)); });
|
||||
@@ -2039,6 +2001,78 @@ void GuiApplication::ShareLocalCursorState() {
|
||||
}
|
||||
}
|
||||
|
||||
bool GuiApplication::RequestPasswordChange(const std::string &password) {
|
||||
if (password.size() != 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!peer_ || !signal_connected_) {
|
||||
offline_warning_text_ =
|
||||
localization::signal_disconnected[localization_language_index_];
|
||||
show_offline_warning_window_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string request_id;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
if (password_change_pending_) {
|
||||
return true;
|
||||
}
|
||||
request_id =
|
||||
CreatePasswordChangeRequestId(++next_password_change_request_id_);
|
||||
password_change_pending_ = true;
|
||||
password_change_result_ready_ = false;
|
||||
password_change_succeeded_ = false;
|
||||
password_change_result_uncertain_ = false;
|
||||
password_change_requested_at_ = std::chrono::steady_clock::now();
|
||||
pending_password_change_request_id_ = request_id;
|
||||
pending_local_password_ = password;
|
||||
password_change_error_.clear();
|
||||
}
|
||||
|
||||
const bool self_hosted = config_center_->IsSelfHosted();
|
||||
const std::string server_host =
|
||||
self_hosted ? config_center_->GetSignalServerHost()
|
||||
: config_center_->GetDefaultServerHost();
|
||||
const int server_port =
|
||||
self_hosted ? config_center_->GetSignalServerPort()
|
||||
: config_center_->GetDefaultSignalServerPort();
|
||||
const std::string pending_identity = std::string(client_id_) + "@" + password;
|
||||
if (!settings_.StagePendingPasswordChange(
|
||||
pending_identity, self_hosted, server_host, server_port, request_id)) {
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
password_change_pending_ = false;
|
||||
pending_password_change_request_id_.clear();
|
||||
pending_local_password_.clear();
|
||||
password_change_error_.clear();
|
||||
offline_warning_text_ = localization::failed[localization_language_index_];
|
||||
show_offline_warning_window_ = true;
|
||||
LOG_ERROR("Could not durably stage password change");
|
||||
return true;
|
||||
}
|
||||
|
||||
const nlohmann::json request = {{"type", "change_password"},
|
||||
{"request_id", request_id},
|
||||
{"new_password", password}};
|
||||
const std::string message = request.dump();
|
||||
if (SendSignalMessage(peer_, message.data(), message.size()) != 0) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
password_change_pending_ = false;
|
||||
pending_password_change_request_id_.clear();
|
||||
pending_local_password_.clear();
|
||||
offline_warning_text_ =
|
||||
localization::signal_disconnected[localization_language_index_];
|
||||
show_offline_warning_window_ = true;
|
||||
}
|
||||
if (!settings_.ClearPendingPasswordChange()) {
|
||||
LOG_WARN("Could not clear unsent pending password change");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GuiApplication::HandlePasswordChangeResult() {
|
||||
bool succeeded = false;
|
||||
bool uncertain = false;
|
||||
@@ -2188,6 +2222,10 @@ void GuiApplication::SyncMainWindow() {
|
||||
ui_->main->set_local_id(UiText(FormatPeerId(client_id_)));
|
||||
ui_->main->set_local_password(password_saved_);
|
||||
ui_->main->set_password_visible(show_password_);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(password_change_mutex_);
|
||||
ui_->main->set_password_change_pending(password_change_pending_);
|
||||
}
|
||||
ui_->main->set_signal_connected(signal_connected_);
|
||||
ui_->main->set_signal_tls_error(signal_status_ ==
|
||||
SignalStatus::SignalTlsCertError);
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
void BindServerCallbacks();
|
||||
void Tick();
|
||||
void ShareLocalCursorState();
|
||||
bool RequestPasswordChange(const std::string &password);
|
||||
void HandlePasswordChangeResult();
|
||||
void HandleCredentialRecovery();
|
||||
void SyncMainWindow();
|
||||
@@ -100,4 +101,4 @@ private:
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -31,6 +31,8 @@ struct TranslationRow {
|
||||
X(local_id_copied_to_clipboard, u8"已复制到剪贴板", "Copied to clipboard", \
|
||||
u8"Скопировано в буфер обмена") \
|
||||
X(password, u8"密码", "Password", u8"Пароль") \
|
||||
X(refresh_password, u8"刷新随机密码", "Refresh random password", \
|
||||
u8"Обновить случайный пароль") \
|
||||
X(max_password_len, u8"最大6个字符", "Max 6 chars", u8"Макс. 6 символов") \
|
||||
X(remote_desktop, u8"远程桌面", "Remote Desktop", \
|
||||
u8"Удаленный рабочий стол") \
|
||||
|
||||
@@ -41,6 +41,7 @@ export global FontAwesomeIcons {
|
||||
out property <string> keyboard: "\u{f11c}";
|
||||
out property <string> minus: "\u{f068}";
|
||||
out property <string> pen: "\u{f304}";
|
||||
out property <string> rotate: "\u{f2f1}";
|
||||
out property <string> signal: "\u{f012}";
|
||||
out property <string> shield-halved: "\u{f3ed}";
|
||||
out property <string> square-full: "\u{f45c}";
|
||||
@@ -54,6 +55,7 @@ export global FontAwesomeIcons {
|
||||
export component IconButton inherits Rectangle {
|
||||
in property <string> icon;
|
||||
in property <string> tooltip;
|
||||
in property <bool> enabled: true;
|
||||
in property <bool> danger: false;
|
||||
in property <length> icon-size: 16px;
|
||||
in property <color> icon-color: #24272d;
|
||||
@@ -69,6 +71,7 @@ export component IconButton inherits Rectangle {
|
||||
: touch.has-hover ? (danger ? #f04438 : root.hover-background)
|
||||
: root.normal-background;
|
||||
border-radius: 4px;
|
||||
opacity: root.enabled ? 1 : 0.4;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
@@ -90,6 +93,7 @@ export component IconButton inherits Rectangle {
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
enabled: root.enabled;
|
||||
clicked => { root.clicked(); }
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export global UiStrings {
|
||||
in-out property <string> online: "Online";
|
||||
in-out property <string> offline: "Offline";
|
||||
in-out property <string> password: "Password";
|
||||
in-out property <string> refresh-password: "Refresh random password";
|
||||
in-out property <string> copied: "Local ID copied to clipboard";
|
||||
in-out property <string> connect: "Connect";
|
||||
in-out property <string> settings: "Settings";
|
||||
@@ -208,6 +209,7 @@ export component MainWindow inherits Window {
|
||||
in property <string> local-password: "";
|
||||
in-out property <string> remote-id-input: "";
|
||||
in property <bool> password-visible: true;
|
||||
in property <bool> password-change-pending: false;
|
||||
in property <bool> signal-connected: false;
|
||||
in property <bool> signal-tls-error: false;
|
||||
in property <[RecentConnection]> recent-connections;
|
||||
@@ -262,6 +264,7 @@ export component MainWindow inherits Window {
|
||||
|
||||
callback copy-local-id;
|
||||
callback toggle-password-visibility;
|
||||
callback refresh-password;
|
||||
callback reset-password(string) -> bool;
|
||||
callback format-remote-id(string) -> string;
|
||||
callback connect-requested(string);
|
||||
@@ -676,19 +679,31 @@ export component MainWindow inherits Window {
|
||||
IconButton {
|
||||
x: 174px;
|
||||
y: 133px;
|
||||
width: 29px;
|
||||
width: 24px;
|
||||
height: 30px;
|
||||
icon: root.password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
|
||||
icon-size: 17px;
|
||||
clicked => { root.toggle-password-visibility(); }
|
||||
}
|
||||
IconButton {
|
||||
x: 214px;
|
||||
x: 198px;
|
||||
y: 133px;
|
||||
width: 29px;
|
||||
width: 24px;
|
||||
height: 30px;
|
||||
icon: FontAwesomeIcons.rotate;
|
||||
icon-size: 15px;
|
||||
tooltip: UiStrings.refresh-password;
|
||||
enabled: !root.password-change-pending;
|
||||
clicked => { root.refresh-password(); }
|
||||
}
|
||||
IconButton {
|
||||
x: 222px;
|
||||
y: 133px;
|
||||
width: 24px;
|
||||
height: 30px;
|
||||
icon: FontAwesomeIcons.pen;
|
||||
icon-size: 15px;
|
||||
enabled: !root.password-change-pending;
|
||||
clicked => { root.reset-password-open = true; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ inline int ApplyMainWindowStrings(
|
||||
strings.set_online(Text(localization::online[language]));
|
||||
strings.set_offline(Text(localization::offline[language]));
|
||||
strings.set_password(Text(localization::password[language]));
|
||||
strings.set_refresh_password(Text(localization::refresh_password[language]));
|
||||
strings.set_copied(
|
||||
Text(localization::local_id_copied_to_clipboard[language]));
|
||||
strings.set_connect(Text(localization::connect[language]));
|
||||
|
||||
@@ -392,7 +392,7 @@ function setup_targets()
|
||||
-- version and use fmt's supported runtime-parser fallback here.
|
||||
add_defines("FMT_CONSTEVAL=")
|
||||
add_packages("slint", {public = true})
|
||||
add_packages("libyuv", "tinyfiledialogs")
|
||||
add_packages("libyuv", "tinyfiledialogs", "openssl3")
|
||||
add_rules("slint")
|
||||
if is_config("CROSSDESK_PORTABLE", true) then
|
||||
-- RuntimeState changes layout in portable builds. Consumers of GUI
|
||||
|
||||
Reference in New Issue
Block a user