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>
90 lines
3.0 KiB
Rust
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(())
|
|
}
|