feat: add fprintd fingerprint authentication via greetd multi-stage PAM (v0.6.0)
Fingerprint auth was missing because moongreet rejected multi-stage auth_message sequences from greetd. With pam_fprintd.so in the PAM stack, greetd sends non-secret prompts for fingerprint and secret prompts for password — moongreet now handles both in a loop. - Replace single-pass auth with multi-stage auth_message loop - fprintd D-Bus probe (gio::DBusProxy) for UI feedback only - Fingerprint label shown when device available and fingers enrolled - 60s socket timeout when fingerprint available (pam_fprintd scan time) - Config option: [appearance] fingerprint-enabled (default: true) - Fix: password entry focus loss after auth error (grab_focus while widget was insensitive — now re-enable before grab_focus)
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// ABOUTME: fprintd D-Bus probe for fingerprint device availability.
|
||||
// ABOUTME: Checks if fprintd is running and the user has enrolled fingerprints.
|
||||
|
||||
use gio::prelude::*;
|
||||
use gtk4::gio;
|
||||
|
||||
const FPRINTD_BUS_NAME: &str = "net.reactivated.Fprint";
|
||||
const FPRINTD_MANAGER_PATH: &str = "/net/reactivated/Fprint/Manager";
|
||||
const FPRINTD_MANAGER_IFACE: &str = "net.reactivated.Fprint.Manager";
|
||||
const FPRINTD_DEVICE_IFACE: &str = "net.reactivated.Fprint.Device";
|
||||
|
||||
const DBUS_TIMEOUT_MS: i32 = 3000;
|
||||
|
||||
/// Lightweight fprintd probe — detects device availability and finger enrollment.
|
||||
/// Does NOT perform verification (that happens through greetd/PAM).
|
||||
pub struct FingerprintProbe {
|
||||
device_proxy: Option<gio::DBusProxy>,
|
||||
}
|
||||
|
||||
impl FingerprintProbe {
|
||||
/// Create a probe without any D-Bus connections.
|
||||
/// Call `init_async().await` to connect to fprintd.
|
||||
pub fn new() -> Self {
|
||||
FingerprintProbe {
|
||||
device_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to fprintd on the system bus and discover the default device.
|
||||
pub async fn init_async(&mut self) {
|
||||
let manager = match gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::System,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
FPRINTD_BUS_NAME,
|
||||
FPRINTD_MANAGER_PATH,
|
||||
FPRINTD_MANAGER_IFACE,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
log::debug!("fprintd manager not available: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = match manager
|
||||
.call_future("GetDefaultDevice", None, gio::DBusCallFlags::NONE, DBUS_TIMEOUT_MS)
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
log::debug!("fprintd GetDefaultDevice failed: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let device_path = match result.child_value(0).get::<String>() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
log::debug!("fprintd: unexpected GetDefaultDevice response type");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if device_path.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
match gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::System,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
FPRINTD_BUS_NAME,
|
||||
&device_path,
|
||||
FPRINTD_DEVICE_IFACE,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(proxy) => {
|
||||
self.device_proxy = Some(proxy);
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("fprintd device proxy failed: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the user has enrolled fingerprints on the default device.
|
||||
/// Returns false if fprintd is unavailable or the user has no enrollments.
|
||||
pub async fn is_available_async(&self, username: &str) -> bool {
|
||||
let proxy = match &self.device_proxy {
|
||||
Some(p) => p,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let args = glib::Variant::from((&username,));
|
||||
match proxy
|
||||
.call_future(
|
||||
"ListEnrolledFingers",
|
||||
Some(&args),
|
||||
gio::DBusCallFlags::NONE,
|
||||
DBUS_TIMEOUT_MS,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => match result.child_value(0).get::<Vec<String>>() {
|
||||
Some(fingers) => !fingers.is_empty(),
|
||||
None => {
|
||||
log::debug!("fprintd: unexpected ListEnrolledFingers response type");
|
||||
false
|
||||
}
|
||||
},
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn new_probe_has_no_device() {
|
||||
let probe = FingerprintProbe::new();
|
||||
assert!(probe.device_proxy.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constants_are_defined() {
|
||||
assert!(!FPRINTD_BUS_NAME.is_empty());
|
||||
assert!(!FPRINTD_MANAGER_PATH.is_empty());
|
||||
assert!(!FPRINTD_MANAGER_IFACE.is_empty());
|
||||
assert!(!FPRINTD_DEVICE_IFACE.is_empty());
|
||||
assert!(DBUS_TIMEOUT_MS > 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user