Implement TUI authentication and inactivity relock

This commit is contained in:
Hermes Agent
2026-08-10 06:22:23 +00:00
parent 91f58bae3a
commit 57a79b7d21
6 changed files with 365 additions and 19 deletions

View File

@@ -3,6 +3,13 @@
use std::{
sync::mpsc::{self, Receiver, Sender},
thread,
time::Duration,
};
use ironstorage::{
authentication::{NativeAuthenticationHandle, NativeAuthenticationSession},
crypto::KeyInfo,
secret_store::SecretProtectionPolicy,
};
use crate::app::{AsyncPayload, AsyncResult, RequestToken};
@@ -12,6 +19,125 @@ pub struct AsyncExecutor {
receiver: Receiver<AsyncResult>,
}
#[derive(Debug, Eq, PartialEq)]
pub enum AuthenticationEvent {
Granted(String),
Failed(String),
Expired,
}
struct AuthenticationCompletion {
generation: u64,
entry: String,
result: Result<NativeAuthenticationHandle, String>,
}
pub struct AuthenticationCoordinator {
session: NativeAuthenticationSession,
key: KeyInfo,
handle: Option<NativeAuthenticationHandle>,
sender: Sender<AuthenticationCompletion>,
receiver: Receiver<AuthenticationCompletion>,
generation: u64,
}
impl AuthenticationCoordinator {
pub fn system(
timeout: ironstorage::authentication::AuthenticationTimeout,
key: KeyInfo,
) -> Result<Self, String> {
let session =
NativeAuthenticationSession::system(SecretProtectionPolicy::default(), timeout)
.map_err(|error| error.to_string())?;
let (sender, receiver) = mpsc::channel();
Ok(Self {
session,
key,
handle: None,
sender,
receiver,
generation: 0,
})
}
pub fn request(&mut self, entry: String) {
self.generation = self.generation.wrapping_add(1);
let generation = self.generation;
let session = self.session.clone();
let key = self.key.clone();
let sender = self.sender.clone();
thread::spawn(move || {
let result = session
.authenticate(&key)
.map_err(|error| error.to_string());
let _ignored = sender.send(AuthenticationCompletion {
generation,
entry,
result,
});
});
}
pub fn completion(&mut self) -> Option<AuthenticationEvent> {
let completion = self
.receiver
.try_iter()
.filter(|completion| completion.generation == self.generation)
.last()?;
match completion.result {
Ok(handle) => {
self.handle = Some(handle);
Some(AuthenticationEvent::Granted(completion.entry))
}
Err(error) => {
self.handle = None;
Some(AuthenticationEvent::Failed(error))
}
}
}
pub fn touch_user_activity(&mut self) -> Option<AuthenticationEvent> {
let handle = self.handle.as_ref()?;
if let Err(error) = handle.touch_user_activity() {
self.handle = None;
return Some(AuthenticationEvent::Failed(error.to_string()));
}
None
}
pub fn poll_lease(&mut self) -> Option<AuthenticationEvent> {
match self.session.expire() {
Ok(true) => {
self.handle = None;
Some(AuthenticationEvent::Expired)
}
Ok(false) => None,
Err(error) => {
self.handle = None;
Some(AuthenticationEvent::Failed(error.to_string()))
}
}
}
pub fn remaining_time(&self) -> Option<Duration> {
self.handle
.as_ref()
.and_then(|handle| handle.remaining_time().ok())
}
pub fn handle(&self) -> Option<NativeAuthenticationHandle> {
self.handle.clone()
}
pub fn lock(&mut self) -> Result<(), String> {
self.generation = self.generation.wrapping_add(1);
self.handle = None;
self.session
.manual_lock()
.map_err(|error| error.to_string())
}
}
impl Default for AsyncExecutor {
fn default() -> Self {
Self::new()