mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-30 09:56:41 +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 <remote_action.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
#include <slint.h>
|
#include <slint.h>
|
||||||
#include <tinyfiledialogs.h>
|
#include <tinyfiledialogs.h>
|
||||||
|
|
||||||
@@ -65,6 +66,28 @@ namespace {
|
|||||||
|
|
||||||
using namespace std::chrono_literals;
|
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) {
|
std::string CreatePasswordChangeRequestId(uint64_t sequence) {
|
||||||
const auto timestamp = std::chrono::system_clock::now()
|
const auto timestamp = std::chrono::system_clock::now()
|
||||||
.time_since_epoch()
|
.time_since_epoch()
|
||||||
@@ -1294,78 +1317,17 @@ void GuiApplication::BindMainCallbacks() {
|
|||||||
ui_->main->set_password_visible(show_password_);
|
ui_->main->set_password_visible(show_password_);
|
||||||
});
|
});
|
||||||
main->on_reset_password([this](slint::SharedString value) {
|
main->on_reset_password([this](slint::SharedString value) {
|
||||||
const std::string password(value);
|
return RequestPasswordChange(std::string(value));
|
||||||
if (password.size() != 6) {
|
});
|
||||||
return false;
|
main->on_refresh_password([this] {
|
||||||
}
|
const auto password = GenerateRandomPassword(password_saved_);
|
||||||
|
if (!password) {
|
||||||
if (!peer_ || !signal_connected_) {
|
LOG_ERROR("Could not generate a random password");
|
||||||
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_];
|
offline_warning_text_ = localization::failed[localization_language_index_];
|
||||||
show_offline_warning_window_ = true;
|
show_offline_warning_window_ = true;
|
||||||
LOG_ERROR("Could not durably stage password change");
|
return;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
RequestPasswordChange(*password);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
main->on_connect_requested(
|
main->on_connect_requested(
|
||||||
[this](slint::SharedString id) { ConnectFromUi(std::string(id)); });
|
[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() {
|
void GuiApplication::HandlePasswordChangeResult() {
|
||||||
bool succeeded = false;
|
bool succeeded = false;
|
||||||
bool uncertain = false;
|
bool uncertain = false;
|
||||||
@@ -2188,6 +2222,10 @@ void GuiApplication::SyncMainWindow() {
|
|||||||
ui_->main->set_local_id(UiText(FormatPeerId(client_id_)));
|
ui_->main->set_local_id(UiText(FormatPeerId(client_id_)));
|
||||||
ui_->main->set_local_password(password_saved_);
|
ui_->main->set_local_password(password_saved_);
|
||||||
ui_->main->set_password_visible(show_password_);
|
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_connected(signal_connected_);
|
||||||
ui_->main->set_signal_tls_error(signal_status_ ==
|
ui_->main->set_signal_tls_error(signal_status_ ==
|
||||||
SignalStatus::SignalTlsCertError);
|
SignalStatus::SignalTlsCertError);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ private:
|
|||||||
void BindServerCallbacks();
|
void BindServerCallbacks();
|
||||||
void Tick();
|
void Tick();
|
||||||
void ShareLocalCursorState();
|
void ShareLocalCursorState();
|
||||||
|
bool RequestPasswordChange(const std::string &password);
|
||||||
void HandlePasswordChangeResult();
|
void HandlePasswordChangeResult();
|
||||||
void HandleCredentialRecovery();
|
void HandleCredentialRecovery();
|
||||||
void SyncMainWindow();
|
void SyncMainWindow();
|
||||||
@@ -100,4 +101,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ struct TranslationRow {
|
|||||||
X(local_id_copied_to_clipboard, u8"已复制到剪贴板", "Copied to clipboard", \
|
X(local_id_copied_to_clipboard, u8"已复制到剪贴板", "Copied to clipboard", \
|
||||||
u8"Скопировано в буфер обмена") \
|
u8"Скопировано в буфер обмена") \
|
||||||
X(password, u8"密码", "Password", 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(max_password_len, u8"最大6个字符", "Max 6 chars", u8"Макс. 6 символов") \
|
||||||
X(remote_desktop, u8"远程桌面", "Remote Desktop", \
|
X(remote_desktop, u8"远程桌面", "Remote Desktop", \
|
||||||
u8"Удаленный рабочий стол") \
|
u8"Удаленный рабочий стол") \
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export global FontAwesomeIcons {
|
|||||||
out property <string> keyboard: "\u{f11c}";
|
out property <string> keyboard: "\u{f11c}";
|
||||||
out property <string> minus: "\u{f068}";
|
out property <string> minus: "\u{f068}";
|
||||||
out property <string> pen: "\u{f304}";
|
out property <string> pen: "\u{f304}";
|
||||||
|
out property <string> rotate: "\u{f2f1}";
|
||||||
out property <string> signal: "\u{f012}";
|
out property <string> signal: "\u{f012}";
|
||||||
out property <string> shield-halved: "\u{f3ed}";
|
out property <string> shield-halved: "\u{f3ed}";
|
||||||
out property <string> square-full: "\u{f45c}";
|
out property <string> square-full: "\u{f45c}";
|
||||||
@@ -54,6 +55,7 @@ export global FontAwesomeIcons {
|
|||||||
export component IconButton inherits Rectangle {
|
export component IconButton inherits Rectangle {
|
||||||
in property <string> icon;
|
in property <string> icon;
|
||||||
in property <string> tooltip;
|
in property <string> tooltip;
|
||||||
|
in property <bool> enabled: true;
|
||||||
in property <bool> danger: false;
|
in property <bool> danger: false;
|
||||||
in property <length> icon-size: 16px;
|
in property <length> icon-size: 16px;
|
||||||
in property <color> icon-color: #24272d;
|
in property <color> icon-color: #24272d;
|
||||||
@@ -69,6 +71,7 @@ export component IconButton inherits Rectangle {
|
|||||||
: touch.has-hover ? (danger ? #f04438 : root.hover-background)
|
: touch.has-hover ? (danger ? #f04438 : root.hover-background)
|
||||||
: root.normal-background;
|
: root.normal-background;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
opacity: root.enabled ? 1 : 0.4;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: root.icon;
|
text: root.icon;
|
||||||
@@ -90,6 +93,7 @@ export component IconButton inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
touch := TouchArea {
|
touch := TouchArea {
|
||||||
|
enabled: root.enabled;
|
||||||
clicked => { root.clicked(); }
|
clicked => { root.clicked(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export global UiStrings {
|
|||||||
in-out property <string> online: "Online";
|
in-out property <string> online: "Online";
|
||||||
in-out property <string> offline: "Offline";
|
in-out property <string> offline: "Offline";
|
||||||
in-out property <string> password: "Password";
|
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> copied: "Local ID copied to clipboard";
|
||||||
in-out property <string> connect: "Connect";
|
in-out property <string> connect: "Connect";
|
||||||
in-out property <string> settings: "Settings";
|
in-out property <string> settings: "Settings";
|
||||||
@@ -208,6 +209,7 @@ export component MainWindow inherits Window {
|
|||||||
in property <string> local-password: "";
|
in property <string> local-password: "";
|
||||||
in-out property <string> remote-id-input: "";
|
in-out property <string> remote-id-input: "";
|
||||||
in property <bool> password-visible: true;
|
in property <bool> password-visible: true;
|
||||||
|
in property <bool> password-change-pending: false;
|
||||||
in property <bool> signal-connected: false;
|
in property <bool> signal-connected: false;
|
||||||
in property <bool> signal-tls-error: false;
|
in property <bool> signal-tls-error: false;
|
||||||
in property <[RecentConnection]> recent-connections;
|
in property <[RecentConnection]> recent-connections;
|
||||||
@@ -262,6 +264,7 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
callback copy-local-id;
|
callback copy-local-id;
|
||||||
callback toggle-password-visibility;
|
callback toggle-password-visibility;
|
||||||
|
callback refresh-password;
|
||||||
callback reset-password(string) -> bool;
|
callback reset-password(string) -> bool;
|
||||||
callback format-remote-id(string) -> string;
|
callback format-remote-id(string) -> string;
|
||||||
callback connect-requested(string);
|
callback connect-requested(string);
|
||||||
@@ -676,19 +679,31 @@ export component MainWindow inherits Window {
|
|||||||
IconButton {
|
IconButton {
|
||||||
x: 174px;
|
x: 174px;
|
||||||
y: 133px;
|
y: 133px;
|
||||||
width: 29px;
|
width: 24px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
icon: root.password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
|
icon: root.password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
|
||||||
icon-size: 17px;
|
icon-size: 17px;
|
||||||
clicked => { root.toggle-password-visibility(); }
|
clicked => { root.toggle-password-visibility(); }
|
||||||
}
|
}
|
||||||
IconButton {
|
IconButton {
|
||||||
x: 214px;
|
x: 198px;
|
||||||
y: 133px;
|
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;
|
height: 30px;
|
||||||
icon: FontAwesomeIcons.pen;
|
icon: FontAwesomeIcons.pen;
|
||||||
icon-size: 15px;
|
icon-size: 15px;
|
||||||
|
enabled: !root.password-change-pending;
|
||||||
clicked => { root.reset-password-open = true; }
|
clicked => { root.reset-password-open = true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ inline int ApplyMainWindowStrings(
|
|||||||
strings.set_online(Text(localization::online[language]));
|
strings.set_online(Text(localization::online[language]));
|
||||||
strings.set_offline(Text(localization::offline[language]));
|
strings.set_offline(Text(localization::offline[language]));
|
||||||
strings.set_password(Text(localization::password[language]));
|
strings.set_password(Text(localization::password[language]));
|
||||||
|
strings.set_refresh_password(Text(localization::refresh_password[language]));
|
||||||
strings.set_copied(
|
strings.set_copied(
|
||||||
Text(localization::local_id_copied_to_clipboard[language]));
|
Text(localization::local_id_copied_to_clipboard[language]));
|
||||||
strings.set_connect(Text(localization::connect[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.
|
-- version and use fmt's supported runtime-parser fallback here.
|
||||||
add_defines("FMT_CONSTEVAL=")
|
add_defines("FMT_CONSTEVAL=")
|
||||||
add_packages("slint", {public = true})
|
add_packages("slint", {public = true})
|
||||||
add_packages("libyuv", "tinyfiledialogs")
|
add_packages("libyuv", "tinyfiledialogs", "openssl3")
|
||||||
add_rules("slint")
|
add_rules("slint")
|
||||||
if is_config("CROSSDESK_PORTABLE", true) then
|
if is_config("CROSSDESK_PORTABLE", true) then
|
||||||
-- RuntimeState changes layout in portable builds. Consumers of GUI
|
-- RuntimeState changes layout in portable builds. Consumers of GUI
|
||||||
|
|||||||
Reference in New Issue
Block a user