feat(settings): redesign the PIN setup/change/remove modals onto reference design
Migrate the three PIN dialogs in App::renderPinDialogs (Set PIN / Change PIN /
Remove PIN) onto the settings-modal reference design — presentation-only, plus a
deliberate secret-hygiene addition:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 420/420/400,
idSuffix pinsetup/pinchange/pinremove}) replacing the legacy positional overload
- drop each dialog's redundant post-description Separator
- every rendered-chrome string -> TR(): titles/buttons reuse settings_set_pin /
settings_change_pin / settings_remove_pin, the Set-PIN confirm reuses
wiz_pin_confirm; 7 new pin_* keys added to the English source + all 8
res/lang/*.json additively; ImGui::Text -> TextUnformatted for labels
- HARDENING: wipe the passphrase/PIN input buffers on BlurFloat dismiss
(X/Esc/outside-click) for all three dialogs. Set PIN's wallet-passphrase buffer
(pin_passphrase_buf_) was previously left resident on dismiss; it is now zeroed.
The vault/RPC path is byte-for-byte unchanged: the submit-time memsets, the
walletpassphrase/walletlock RPCs, vault_->store/changePin/retrieve/removeVault,
SecureVault::secureZero, and setPinEnabled/save are untouched. The pin_status_ and
toast strings inside the worker callbacks are left in English (a separate
service-layer i18n pass), to avoid editing the secret path.
Verified: build + ctest + hygiene clean; i18n complete in 8 langs with no format
specifiers; a 3-lens adversarial review (secret-hygiene / presentation-only /
ui-i18n) returned zero findings, with the presentation lens proving the vault path
diffs byte-for-byte identical against HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1816,29 +1816,28 @@ void App::renderPinDialogs() {
|
||||
|
||||
// ---- Set PIN dialog ----
|
||||
if (show_pin_setup_) {
|
||||
if (BeginOverlayDialog("Set PIN", &show_pin_setup_, 420.0f, 0.94f)) {
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Set a 4-8 digit PIN for quick wallet unlock. "
|
||||
"Your wallet passphrase will be encrypted with this PIN "
|
||||
"and stored locally.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("settings_set_pin"); ov.p_open = &show_pin_setup_;
|
||||
ov.style = OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 420.0f; ov.idSuffix = "pinsetup";
|
||||
if (BeginOverlayDialog(ov)) {
|
||||
|
||||
ImGui::TextWrapped("%s", TR("pin_setup_desc"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Wallet Passphrase:");
|
||||
ImGui::TextUnformatted(TR("pin_wallet_passphrase"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_passphrase", pin_passphrase_buf_, sizeof(pin_passphrase_buf_),
|
||||
ImGuiInputTextFlags_Password);
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::Text("New PIN (4-8 digits):");
|
||||
ImGui::TextUnformatted(TR("pin_new_label"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_new", pin_buf_, sizeof(pin_buf_),
|
||||
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::Text("Confirm PIN:");
|
||||
ImGui::TextUnformatted(TR("wiz_pin_confirm"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_confirm", pin_confirm_buf_, sizeof(pin_confirm_buf_),
|
||||
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
|
||||
@@ -1855,7 +1854,7 @@ void App::renderPinDialogs() {
|
||||
strcmp(pin_buf_, pin_confirm_buf_) == 0;
|
||||
|
||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||
if (ui::material::TactileButton("Set PIN", ImVec2(-1, 40))) {
|
||||
if (ui::material::TactileButton(TR("settings_set_pin"), ImVec2(-1, 40))) {
|
||||
pin_in_progress_ = true;
|
||||
pin_status_ = "Verifying passphrase...";
|
||||
|
||||
@@ -1911,30 +1910,39 @@ void App::renderPinDialogs() {
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
// Wipe the passphrase/PIN buffers if the dialog was dismissed (X / Esc /
|
||||
// outside-click) without submitting. The submit path already memsets them.
|
||||
if (!show_pin_setup_) {
|
||||
memset(pin_passphrase_buf_, 0, sizeof(pin_passphrase_buf_));
|
||||
memset(pin_buf_, 0, sizeof(pin_buf_));
|
||||
memset(pin_confirm_buf_, 0, sizeof(pin_confirm_buf_));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Change PIN dialog ----
|
||||
if (show_pin_change_) {
|
||||
if (BeginOverlayDialog("Change PIN", &show_pin_change_, 420.0f, 0.94f)) {
|
||||
|
||||
ImGui::TextWrapped("Change your unlock PIN. You need your current PIN and a new PIN.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("settings_change_pin"); ov.p_open = &show_pin_change_;
|
||||
ov.style = OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 420.0f; ov.idSuffix = "pinchange";
|
||||
if (BeginOverlayDialog(ov)) {
|
||||
|
||||
ImGui::TextWrapped("%s", TR("pin_change_desc"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Current PIN:");
|
||||
ImGui::TextUnformatted(TR("pin_current_label"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_old", pin_old_buf_, sizeof(pin_old_buf_),
|
||||
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::Text("New PIN (4-8 digits):");
|
||||
ImGui::TextUnformatted(TR("pin_new_label"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_change_new", pin_buf_, sizeof(pin_buf_),
|
||||
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::Text("Confirm New PIN:");
|
||||
ImGui::TextUnformatted(TR("pin_confirm_new_label"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_change_confirm", pin_confirm_buf_, sizeof(pin_confirm_buf_),
|
||||
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
|
||||
@@ -1951,7 +1959,7 @@ void App::renderPinDialogs() {
|
||||
strcmp(pin_buf_, pin_confirm_buf_) == 0;
|
||||
|
||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||
if (ui::material::TactileButton("Change PIN", ImVec2(-1, 40))) {
|
||||
if (ui::material::TactileButton(TR("settings_change_pin"), ImVec2(-1, 40))) {
|
||||
pin_in_progress_ = true;
|
||||
pin_status_ = "Changing PIN...";
|
||||
std::string oldPin(pin_old_buf_);
|
||||
@@ -1984,20 +1992,26 @@ void App::renderPinDialogs() {
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
// Wipe the PIN buffers if the dialog was dismissed without submitting.
|
||||
if (!show_pin_change_) {
|
||||
memset(pin_old_buf_, 0, sizeof(pin_old_buf_));
|
||||
memset(pin_buf_, 0, sizeof(pin_buf_));
|
||||
memset(pin_confirm_buf_, 0, sizeof(pin_confirm_buf_));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Remove PIN dialog ----
|
||||
if (show_pin_remove_) {
|
||||
if (BeginOverlayDialog("Remove PIN", &show_pin_remove_, 400.0f, 0.94f)) {
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Enter your current PIN to confirm removal. "
|
||||
"You will need to use your full passphrase to unlock.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("settings_remove_pin"); ov.p_open = &show_pin_remove_;
|
||||
ov.style = OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 400.0f; ov.idSuffix = "pinremove";
|
||||
if (BeginOverlayDialog(ov)) {
|
||||
|
||||
ImGui::TextWrapped("%s", TR("pin_remove_desc"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Current PIN:");
|
||||
ImGui::TextUnformatted(TR("pin_current_label"));
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##pin_remove", pin_old_buf_, sizeof(pin_old_buf_),
|
||||
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
|
||||
@@ -2010,7 +2024,7 @@ void App::renderPinDialogs() {
|
||||
ImGui::Spacing();
|
||||
bool valid = strlen(pin_old_buf_) >= 4;
|
||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||
if (ui::material::TactileButton("Remove PIN", ImVec2(-1, 40))) {
|
||||
if (ui::material::TactileButton(TR("settings_remove_pin"), ImVec2(-1, 40))) {
|
||||
pin_in_progress_ = true;
|
||||
pin_status_ = "Verifying PIN...";
|
||||
std::string oldPin(pin_old_buf_);
|
||||
@@ -2047,6 +2061,10 @@ void App::renderPinDialogs() {
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
// Wipe the PIN buffer if the dialog was dismissed without submitting.
|
||||
if (!show_pin_remove_) {
|
||||
memset(pin_old_buf_, 0, sizeof(pin_old_buf_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -537,6 +537,14 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["decrypt_success_desc"] = "Your wallet is now unencrypted. A backup of the encrypted wallet was saved as wallet.dat.encrypted.bak in your data directory.";
|
||||
strings_["decrypt_error_title"] = "Decryption failed";
|
||||
strings_["try_again"] = "Try Again";
|
||||
// --- PIN setup / change / remove dialogs (settings) ---
|
||||
strings_["pin_setup_desc"] = "Set a 4-8 digit PIN for quick wallet unlock. Your wallet passphrase will be encrypted with this PIN and stored locally.";
|
||||
strings_["pin_wallet_passphrase"] = "Wallet Passphrase:";
|
||||
strings_["pin_new_label"] = "New PIN (4-8 digits):";
|
||||
strings_["pin_change_desc"] = "Change your unlock PIN. You need your current PIN and a new PIN.";
|
||||
strings_["pin_current_label"] = "Current PIN:";
|
||||
strings_["pin_confirm_new_label"] = "Confirm New PIN:";
|
||||
strings_["pin_remove_desc"] = "Enter your current PIN to confirm removal. You will need to use your full passphrase to unlock.";
|
||||
strings_["wiz_confirm"] = "Confirm:";
|
||||
strings_["wiz_strength_weak"] = "Weak";
|
||||
strings_["wiz_strength_fair"] = "Fair";
|
||||
|
||||
Reference in New Issue
Block a user