#![allow(unexpected_cfgs)]
mod fallback;
use windows::{
core::HSTRING,
Foundation::IAsyncOperation,
Security::Credentials::UI::{
UserConsentVerificationResult, UserConsentVerifier, UserConsentVerifierAvailability,
},
Win32::{
Foundation::RPC_E_CHANGED_MODE,
System::Com::{CoInitializeEx, CoUninitialize, COINIT_MULTITHREADED},
},
};
use crate::{text::WindowsText, BiometricStrength, Error, Result, Text};
pub(crate) type RawContext = ();
#[derive(Debug)]
pub(crate) struct Context;
impl Context {
pub(crate) fn new(_: RawContext) -> Self {
Self
}
pub(crate) fn authenticate<F>(
&self,
message: Text,
_: &Policy,
callback: F,
) -> Result<()>
where
F: Fn(Result<()>) + Send + 'static,
{
let title = message.windows.title.to_owned();
let description = message.windows.description.to_owned();
std::thread::Builder::new()
.name("robius-authentication".into())
.spawn(move || {
let text = WindowsText {
title: &title,
description: &description,
};
let com = ComGuard::new_multithreaded();
callback(authenticate_blocking(text));
drop(com);
})
.map_err(|_| Error::Unavailable)?;
Ok(())
}
}
struct ComGuard {
should_uninit: bool,
}
impl ComGuard {
fn new_multithreaded() -> Self {
let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
Self {
should_uninit: hr != RPC_E_CHANGED_MODE,
}
}
}
impl Drop for ComGuard {
fn drop(&mut self) {
if self.should_uninit {
unsafe { CoUninitialize() };
}
}
}
fn authenticate_blocking(text: WindowsText<'_, '_>) -> Result<()> {
let available =
check_availability()?.get() == Ok(UserConsentVerifierAvailability::Available);
if available {
let verification = request_verification(text)?;
convert(verification.get()?)
} else {
fallback::authenticate(text)
}
}
#[derive(Debug)]
pub(crate) struct Policy;
impl Policy {
#[inline]
pub(crate) fn set_action_id(&mut self, _: String) -> Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct PolicyBuilder {
biometrics: bool,
password: bool,
}
impl PolicyBuilder {
pub(crate) const fn new() -> Self {
Self {
biometrics: true,
password: true,
}
}
pub(crate) const fn biometrics(self, biometrics: Option<BiometricStrength>) -> Self {
Self {
biometrics: biometrics.is_some(),
..self
}
}
pub(crate) const fn password(self, password: bool) -> Self {
Self { password, ..self }
}
pub(crate) const fn companion(self, _: bool) -> Self {
self
}
pub(crate) const fn wrist_detection(self, _: bool) -> Self {
self
}
pub(crate) fn action_ids(self, _: Vec<String>) -> Self {
self
}
pub(crate) const fn build(self) -> Option<Policy> {
if self.biometrics && self.password {
Some(Policy)
} else {
None
}
}
}
fn check_availability() -> Result<IAsyncOperation<UserConsentVerifierAvailability>> {
UserConsentVerifier::CheckAvailabilityAsync().map_err(|e| e.into())
}
#[cfg(feature = "uwp")]
fn request_verification(
text: WindowsText,
) -> Result<IAsyncOperation<UserConsentVerificationResult>> {
UserConsentVerifier::RequestVerificationAsync(&HSTRING::from(text.description))
.map_err(|e| e.into())
}
#[cfg(not(feature = "uwp"))]
fn request_verification(
text: WindowsText,
) -> Result<IAsyncOperation<UserConsentVerificationResult>> {
use windows::{
core::{factory, s},
Win32::{
Foundation::HWND,
System::WinRT::IUserConsentVerifierInterop,
UI::{
Input::KeyboardAndMouse::{
keybd_event, GetAsyncKeyState, SetFocus, KEYEVENTF_EXTENDEDKEY,
KEYEVENTF_KEYUP, VK_MENU,
},
WindowsAndMessaging::{FindWindowA, GetDesktopWindow, SetForegroundWindow},
},
},
};
fn focus_security_prompt() -> Result<()> {
unsafe fn try_find_and_set_focus(
class_name: windows::core::PCSTR,
) -> retry::OperationResult<(), ()> {
let hwnd = unsafe { FindWindowA(class_name, None) };
if hwnd.0 != 0 {
set_focus(hwnd);
return retry::OperationResult::Ok(());
}
retry::OperationResult::Retry(())
}
let class_name = s!("Credential Dialog Xaml Host");
retry::retry_with_index(retry::delay::Fixed::from_millis(500), |current_try| {
if current_try > 3 {
return retry::OperationResult::Err(());
}
unsafe { try_find_and_set_focus(class_name) }
})
.map_err(|_| Error::Unknown)
}
fn set_focus(window: HWND) {
let mut pressed = false;
unsafe {
if GetAsyncKeyState(VK_MENU.0 as i32) >= 0 {
pressed = true;
keybd_event(VK_MENU.0 as u8, 0, KEYEVENTF_EXTENDEDKEY, 0);
}
let _ = SetForegroundWindow(window);
SetFocus(window);
if pressed {
keybd_event(
VK_MENU.0 as u8,
0,
KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP,
0,
);
}
}
}
let window = unsafe { GetDesktopWindow() };
let factory = factory::<UserConsentVerifier, IUserConsentVerifierInterop>()?;
let op = unsafe {
IUserConsentVerifierInterop::RequestVerificationForWindowAsync(
&factory,
window,
&HSTRING::from(text.description),
)
}?;
let _ = focus_security_prompt();
Ok(op)
}
fn convert(result: UserConsentVerificationResult) -> Result<()> {
match result {
UserConsentVerificationResult::Verified => Ok(()),
UserConsentVerificationResult::DeviceNotPresent => Err(Error::Unavailable),
UserConsentVerificationResult::NotConfiguredForUser => Err(Error::NotConfigured),
UserConsentVerificationResult::DisabledByPolicy => Err(Error::DisabledByPolicy),
UserConsentVerificationResult::DeviceBusy => Err(Error::Busy),
UserConsentVerificationResult::RetriesExhausted => Err(Error::Exhausted),
UserConsentVerificationResult::Canceled => Err(Error::UserCanceled),
_ => Err(Error::Unknown),
}
}
impl From<windows::core::Error> for Error {
fn from(_value: windows::core::Error) -> Self {
Self::Unknown
}
}