Skip to content

Commit 71eaef7

Browse files
committed
refactor: improve UV module code quality and maintainability
- Use thiserror derive for VerificationError (removes boilerplate) - Define priority constants module (FPRINTD=100, FACE=90, COMMAND=50, NOTIFICATION=10) - Add config validation for face threshold (0.0-1.0 range) - Cache tokio runtime in FprintdProvider (reuse across verify calls) - Add from_config factory methods to all providers - Simplify build_uv_manager with extend pattern
1 parent 11cdd41 commit 71eaef7

10 files changed

Lines changed: 221 additions & 86 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/passless/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ zeroize.workspace = true
4242
hex.workspace = true
4343
shadow-rs.workspace = true
4444
dirs.workspace = true
45+
thiserror.workspace = true
4546
hmac = "0.12"
4647
rpassword = "7.3"
4748
atty = "0.2"

cmd/passless/src/authenticator.rs

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::pin_storage::PinStorage;
22
use crate::storage::{CredentialFilter, CredentialStorage};
33
use crate::util::bytes_to_hex;
44
use crate::uv::{
5-
FprintdProvider, NotificationProvider, UserVerificationManager, UserVerificationProvider,
6-
VerificationContext, VerificationResult,
5+
CommandProvider, FprintdProvider, NotificationProvider, UserVerificationManager,
6+
UserVerificationProvider, VerificationContext, VerificationResult,
77
};
88

99
#[cfg(feature = "face")]
@@ -141,49 +141,37 @@ fn build_uv_manager(config: &UvConfig) -> UserVerificationManager {
141141

142142
match config.provider {
143143
UvProviderType::Auto => {
144-
if config.fprintd.enabled {
145-
providers.push(Box::new(FprintdProvider::new()));
146-
}
144+
providers.extend(FprintdProvider::from_config(config.fprintd.enabled));
147145
#[cfg(feature = "face")]
148-
if config.face.enabled {
149-
providers.push(Box::new(FaceIdProvider::new(
150-
config.face.camera_index,
151-
config.face.threshold,
152-
None,
153-
)));
154-
}
155-
providers.push(Box::new(NotificationProvider::new(config.timeout_seconds)));
146+
providers.extend(FaceIdProvider::from_config(
147+
config.face.enabled,
148+
config.face.camera_index,
149+
config.face.threshold,
150+
));
151+
providers.push(NotificationProvider::from_config(config.timeout_seconds));
156152
}
157153
UvProviderType::Fprintd => {
158-
if config.fprintd.enabled {
159-
providers.push(Box::new(FprintdProvider::new()));
160-
}
154+
providers.extend(FprintdProvider::from_config(config.fprintd.enabled));
161155
}
162156
UvProviderType::Face => {
163157
#[cfg(feature = "face")]
164-
if config.face.enabled {
165-
providers.push(Box::new(FaceIdProvider::new(
166-
config.face.camera_index,
167-
config.face.threshold,
168-
None,
169-
)));
170-
}
158+
providers.extend(FaceIdProvider::from_config(
159+
config.face.enabled,
160+
config.face.camera_index,
161+
config.face.threshold,
162+
));
171163
#[cfg(not(feature = "face"))]
172-
{
173-
log::warn!("Face provider requested but 'face' feature not enabled");
174-
}
164+
log::warn!("Face provider requested but 'face' feature not enabled");
175165
}
176166
UvProviderType::Notification => {
177-
providers.push(Box::new(NotificationProvider::new(config.timeout_seconds)));
167+
providers.push(NotificationProvider::from_config(config.timeout_seconds));
178168
}
179169
UvProviderType::Command => {
180-
if config.command.enabled && !config.command.command.is_empty() {
181-
use crate::uv::CommandProvider;
182-
providers.push(Box::new(CommandProvider::new(
183-
config.command.command.clone(),
184-
config.timeout_seconds,
185-
)));
186-
}
170+
providers.extend(CommandProvider::from_config(
171+
config.command.enabled,
172+
config.command.command.clone(),
173+
config.timeout_seconds,
174+
));
187175
}
188176
}
189177

cmd/passless/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ fn run() -> Result<()> {
242242
// Load config: CLI args + config file + defaults (CLI takes precedence)
243243
let config = AppConfig::load(&mut args);
244244

245+
// Validate configuration
246+
if let Err(e) = config.validate() {
247+
error!("Configuration validation failed: {}", e);
248+
process::exit(1);
249+
}
250+
245251
if config.verbose && log_level != log::LevelFilter::Debug {
246252
info!("Enabling verbose logging...");
247253
log::set_max_level(log::LevelFilter::Debug);

cmd/passless/src/uv/command.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
//! - `PASSLESS_UV_USER`: User identifier (if available)
1717
//! - `PASSLESS_UV_TIMEOUT`: Timeout in seconds
1818
19-
use super::{UserVerificationProvider, VerificationContext, VerificationError, VerificationResult};
19+
use super::{
20+
UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority,
21+
};
2022

2123
use log::{debug, info};
2224

@@ -43,6 +45,19 @@ impl CommandProvider {
4345
}
4446
}
4547

48+
/// Create a provider if enabled and configured
49+
pub fn from_config(
50+
enabled: bool,
51+
command: Vec<String>,
52+
timeout_seconds: u32,
53+
) -> Option<Box<dyn UserVerificationProvider>> {
54+
if enabled && !command.is_empty() {
55+
Some(Box::new(Self::new(command, timeout_seconds)))
56+
} else {
57+
None
58+
}
59+
}
60+
4661
/// Execute the verification command
4762
fn execute(
4863
&self,
@@ -129,7 +144,7 @@ impl UserVerificationProvider for CommandProvider {
129144
}
130145

131146
fn priority(&self) -> u8 {
132-
50
147+
priority::COMMAND
133148
}
134149
}
135150

@@ -146,7 +161,7 @@ mod tests {
146161
#[test]
147162
fn test_command_provider_priority() {
148163
let provider = CommandProvider::new(vec!["echo".to_string()], 30);
149-
assert_eq!(provider.priority(), 50);
164+
assert_eq!(provider.priority(), priority::COMMAND);
150165
}
151166

152167
#[test]

cmd/passless/src/uv/face.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
//! During enrollment, multiple face embeddings are captured and averaged
1414
//! to create a robust template. The template is stored securely.
1515
16-
use super::{UserVerificationProvider, VerificationContext, VerificationError, VerificationResult};
16+
use super::{
17+
UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority,
18+
};
1719

1820
use log::{debug, info, warn};
1921

@@ -58,6 +60,19 @@ impl FaceIdProvider {
5860
}
5961
}
6062

63+
/// Create a provider if enabled in config
64+
pub fn from_config(
65+
enabled: bool,
66+
camera_index: usize,
67+
threshold: f32,
68+
) -> Option<Box<dyn UserVerificationProvider>> {
69+
if enabled {
70+
Some(Box::new(Self::new(camera_index, threshold, None)))
71+
} else {
72+
None
73+
}
74+
}
75+
6176
/// Get path to the stored embeddings file
6277
fn embeddings_file(&self) -> PathBuf {
6378
self.embeddings_path.join("face_template.bin")
@@ -275,7 +290,7 @@ impl UserVerificationProvider for FaceIdProvider {
275290
}
276291

277292
fn priority(&self) -> u8 {
278-
90
293+
priority::FACE
279294
}
280295

281296
fn supports_enrollment(&self) -> bool {
@@ -358,7 +373,7 @@ mod tests {
358373
#[test]
359374
fn test_face_provider_priority() {
360375
let provider = FaceIdProvider::new(0, DEFAULT_THRESHOLD, None);
361-
assert_eq!(provider.priority(), 90);
376+
assert_eq!(provider.priority(), priority::FACE);
362377
}
363378

364379
#[test]

cmd/passless/src/uv/fprintd.rs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,50 @@
88
//! - A fingerprint reader must be connected
99
//! - User must have enrolled fingerprints
1010
11-
use super::{UserVerificationProvider, VerificationContext, VerificationError, VerificationResult};
11+
use super::{
12+
UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority,
13+
};
1214

1315
use log::{debug, error, info, warn};
1416

15-
use std::sync::{Arc, Mutex};
17+
use std::sync::Arc;
18+
use std::sync::Mutex;
1619

1720
/// fprintd-based fingerprint verification provider
1821
pub struct FprintdProvider {
19-
available_cache: Arc<Mutex<Option<bool>>>,
22+
available_cache: Mutex<Option<bool>>,
23+
runtime: Mutex<Option<Arc<tokio::runtime::Runtime>>>,
2024
}
2125

2226
impl FprintdProvider {
2327
/// Create a new fprintd provider
2428
pub fn new() -> Self {
2529
Self {
26-
available_cache: Arc::new(Mutex::new(None)),
30+
available_cache: Mutex::new(None),
31+
runtime: Mutex::new(None),
2732
}
2833
}
2934

35+
/// Create a provider if enabled in config
36+
pub fn from_config(enabled: bool) -> Option<Box<dyn UserVerificationProvider>> {
37+
if enabled {
38+
Some(Box::new(Self::new()))
39+
} else {
40+
None
41+
}
42+
}
43+
44+
/// Get or create the tokio runtime
45+
fn get_runtime(&self) -> Result<Arc<tokio::runtime::Runtime>, VerificationError> {
46+
let mut runtime_guard = self.runtime.lock().unwrap();
47+
if runtime_guard.is_none() {
48+
*runtime_guard = Some(Arc::new(tokio::runtime::Runtime::new().map_err(|e| {
49+
VerificationError::DeviceError(format!("Failed to create runtime: {}", e))
50+
})?));
51+
}
52+
Ok(runtime_guard.as_ref().unwrap().clone())
53+
}
54+
3055
async fn do_verify(timeout_secs: u32) -> Result<VerificationResult, VerificationError> {
3156
use zbus::Connection;
3257

@@ -195,8 +220,8 @@ impl FprintdProvider {
195220
Ok(result)
196221
}
197222

198-
fn check_available_sync() -> bool {
199-
let rt = match tokio::runtime::Runtime::new() {
223+
fn check_available(&self) -> bool {
224+
let rt = match self.get_runtime() {
200225
Ok(rt) => rt,
201226
Err(_) => return false,
202227
};
@@ -252,7 +277,7 @@ impl UserVerificationProvider for FprintdProvider {
252277
return cached;
253278
}
254279

255-
let available = Self::check_available_sync();
280+
let available = self.check_available();
256281
*self.available_cache.lock().unwrap() = Some(available);
257282
available
258283
}
@@ -262,22 +287,13 @@ impl UserVerificationProvider for FprintdProvider {
262287
context: &VerificationContext,
263288
) -> Result<VerificationResult, VerificationError> {
264289
let timeout_secs = context.timeout_seconds;
290+
let rt = self.get_runtime()?;
265291

266-
let result = std::thread::spawn(move || {
267-
let rt = tokio::runtime::Runtime::new().map_err(|e| {
268-
VerificationError::DeviceError(format!("Failed to create runtime: {}", e))
269-
})?;
270-
271-
rt.block_on(async { Self::do_verify(timeout_secs).await })
272-
})
273-
.join()
274-
.map_err(|_| VerificationError::Other("Verification thread panicked".into()))??;
275-
276-
Ok(result)
292+
rt.block_on(async { Self::do_verify(timeout_secs).await })
277293
}
278294

279295
fn priority(&self) -> u8 {
280-
100
296+
priority::FPRINTD
281297
}
282298

283299
fn supports_enrollment(&self) -> bool {
@@ -306,7 +322,7 @@ mod tests {
306322
#[test]
307323
fn test_fprintd_provider_priority() {
308324
let provider = FprintdProvider::new();
309-
assert_eq!(provider.priority(), 100);
325+
assert_eq!(provider.priority(), priority::FPRINTD);
310326
}
311327

312328
#[test]

0 commit comments

Comments
 (0)