Files
authforge/daemon/src/main.rs
michael 45ac398730 feat(daemon): recovery code generation and Argon2id hashing
Pure-logic module: generate 8-digit codes, Argon2id PHC hash with OS RNG salt,
verify via constant-time PasswordVerifier. Dead-code allow until Task 4 wires
the store on top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:25:30 -07:00

90 lines
3.0 KiB
Rust

use anyhow::{Context, Result};
use std::sync::Arc;
use tracing::{info, warn};
mod dbus;
mod fido;
mod lockout;
mod policy_apply;
mod polkit;
mod recovery;
mod state;
mod storage;
const BUS_NAME: &str = "io.dangerousthings.AuthForge";
const OBJECT_PATH: &str = "/io/dangerousthings/AuthForge";
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
info!("authforged {} starting", env!("CARGO_PKG_VERSION"));
let conn = zbus::Connection::system()
.await
.context("connecting to system D-Bus")?;
let polkit = if std::env::var("AUTHFORGE_POLKIT_BYPASS").is_ok() {
warn!(
"AUTHFORGE_POLKIT_BYPASS set — polkit checks are DISABLED. Do not use in production."
);
polkit::Polkit::permissive()
} else {
polkit::Polkit::system(conn.clone()).await
};
let cfg = state::StorageConfig::from_env_or_defaults();
let policy_dir = cfg.policy_dir.clone();
let authn: Arc<dyn fido::authenticator::Authenticator> =
Arc::new(fido::ctap::CtapAuthenticator::new());
let state = Arc::new(state::AppState::open(cfg, authn)?);
let auth = dbus::AuthForge {
state: state.clone(),
polkit: Arc::new(polkit),
};
conn.object_server().at(OBJECT_PATH, auth).await?;
conn.request_name(BUS_NAME)
.await
.context("acquiring well-known bus name (another instance running?)")?;
info!("listening on D-Bus as {BUS_NAME} at {OBJECT_PATH}");
// Inotify watcher → PolicyChanged signal.
let policy_store = storage::policy::PolicyStore::new(policy_dir.clone());
match policy_store.watch() {
Ok((watcher, mut rx)) => {
let conn_for_signal = conn.clone();
tokio::spawn(async move {
while rx.changed().await.is_ok() {
let iface_ref = match conn_for_signal
.object_server()
.interface::<_, dbus::AuthForge>(OBJECT_PATH)
.await
{
Ok(r) => r,
Err(e) => {
warn!(error=%e, "no AuthForge interface; skipping signal");
continue;
}
};
if let Err(e) =
dbus::AuthForge::policy_changed(iface_ref.signal_context()).await
{
warn!(error=%e, "PolicyChanged emit failed");
}
}
});
// Daemon lives until SIGTERM; deliberately leak to keep watcher alive.
std::mem::forget(watcher);
info!("watching {} for policy changes", policy_dir.display());
}
Err(e) => warn!(error=%e, "failed to start policy.d watcher"),
}
// Park forever. systemd will SIGTERM the process when stopping the unit.
std::future::pending::<()>().await;
Ok(())
}