Skip to content

Commit

Permalink
windows: fix accept/deny prompt on Windows
Browse files Browse the repository at this point in the history
Launching the browser is prohibited from a Windows
service. `WTSSendMessageA` seems to be the only thing that works. See
comments in the commit.
  • Loading branch information
benma committed Aug 29, 2024
1 parent 7eab96e commit 67ed12c
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 31 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 1.6.1
- Fix accept/deny prompt on Windows

## 1.6.0

- Prompt to accept/deny a host which is not explicitly allowed
Expand Down
68 changes: 39 additions & 29 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion bitbox-bridge/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "bitbox-bridge"
# If you bump this, also change the ProductCode in bitbox-bridge/release/windows/wix/Product.wxs.
version = "1.6.0"
version = "1.6.1"
authors = ["Niklas Claesson <nicke.claesson@gmail.com>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -22,6 +22,9 @@ warp = "0.3.7"
tera = "1.20"
uuid = { version = "1.10.0", features = ["v4"] }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59.0", features = ["Win32", "Win32_System", "Win32_UI", "Win32_System_RemoteDesktop", "Win32_UI_WindowsAndMessaging"] }

[dependencies.u2fframing]
version = "0.1"
path = "../u2fframing"
Expand Down
2 changes: 1 addition & 1 deletion bitbox-bridge/release/windows/wix/Product.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

<!-- ProductCode should change with every release -->
<?define ProductCode = "{de37ff56-5490-4e50-9fcb-5bb8e8597544}"?>
<?define ProductCode = "{3fe2b668-f725-4884-92e9-7af6e513ccd9}"?>
<!-- UpgradeCode should stay the same foverever (this is the real ID of the app)-->
<?define UpgradeCode = "{dfe7eecb-5dc0-4c30-ba78-a9eff36efa31}"?>

Expand Down
71 changes: 71 additions & 0 deletions bitbox-bridge/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,77 @@ impl AllowedHosts {
}
}

// On windows, we use a native system dialog to prompt the user.
// The browser based prompt does not work as we can't launch a browser from the service (not allowed).
//
// We use WTSSendMessage as officially recommended (it recommends WTSSendMessageA, the ANSI-encoded
// version, but WTSSendMessagW for UTF-16 also works):
//
// See https://learn.microsoft.com/en-us/windows/win32/services/interactive-services
// > You can use the following techniques to interact with the user from a service on all supported versions of Windows:
// > Display a dialog box in the user's session using the [WTSSendMessage](https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtssendmessagea) function.
#[cfg(target_os = "windows")]
async fn user_confirm(
_confirm_state: Arc<ConfirmState>,
message: String,
_base_url: &str,
) -> Result<bool, ()> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::System::RemoteDesktop::{
WTSGetActiveConsoleSessionId, WTSSendMessageW, WTS_CURRENT_SERVER_HANDLE,
};
use windows_sys::Win32::UI::WindowsAndMessaging::{IDYES, MB_YESNO, MESSAGEBOX_RESULT};

fn utf8_to_utf16(s: &str) -> Vec<u16> {
std::ffi::OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}

let title_c = utf8_to_utf16("BitBoxBridge");
let message_c = utf8_to_utf16(&message);

let mut response: MESSAGEBOX_RESULT = 0;
let timeout: u32 = 60; // 60 seconds

unsafe {
// Need the active user session ID so the dialog is shown to the user.
let current_session = WTSGetActiveConsoleSessionId();
if current_session == 0xFFFFFFFF {
// See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-wtsgetactiveconsolesessionid#return-value
return Err(());
}
let result = WTSSendMessageW(
WTS_CURRENT_SERVER_HANDLE,
current_session,
title_c.as_ptr(),
// Each element is 2 bytes except the null terminator, so deduct one.
(title_c.len() * 2 - 1) as u32,
message_c.as_ptr(),
(message_c.len() * 2 - 1) as u32,
MB_YESNO,
timeout,
&mut response,
1,
);

if result == 0 {
return Err(());
}
}

// Check if the user clicked 'Yes'
Ok(response == IDYES)
}

// On Linux/maCOS, we launch a browser with a prompt.
//
// On macOS, native dialogs don't work if there is no main window (we don't have one, it's a service).
//
// On linux, native dialogs would work, but we use the browser based solution here too as native
// dialogs might not work in all distros/configs.
#[cfg(not(target_os = "windows"))]
async fn user_confirm(
confirm_state: Arc<ConfirmState>,
message: String,
Expand Down

0 comments on commit 67ed12c

Please sign in to comment.