Files
authforge/daemon/src/main.rs
michael 24237cbf8b refactor(daemon): AppState delegates to storage modules; dbus tests use tempdir
Lands plan tasks 2.15 (AppState refactor with StorageConfig + open()) and 2.16
(dbus.rs tests switched to tempdir-backed AppState; storage errors threaded
through D-Bus methods as Failed). Bundled because the AppState surface change
forces dbus.rs adjustments in the same commit.

- daemon/src/state.rs: AppState::open(StorageConfig) replaces with_fixtures().
  StorageConfig.from_env_or_defaults() reads AUTHFORGE_POLICY_DIR /
  _PENDING_DIR / _USERDB env vars (defaults: /etc/authforge/policy.d,
  /var/lib/authforge/pending, /var/lib/authforge/users.db). State delegates
  list/add/remove credentials to CredsPathResolver + CredentialsStore picked
  per-call from current Policy; pending and userdb operate independently.
- daemon/src/main.rs: opens state via env-driven config; reuses cfg.policy_dir
  for the watcher to keep one source of truth.
- daemon/src/dbus.rs: every write method maps StateError to fdo::Error::Failed.
  p2p_pair seeds 00-test.conf with [storage] backend = central pointing into
  the tempdir so credential writes don't try to touch /home/<user>/...
  (alice/bob/carol aren't real accounts in tests).
- Renamed: list_credentials_returns_fixture_for_alice ->
  list_credentials_after_enroll. Removed: with_fixtures().
- .gitignore: add .claude/ so leftover Phase 1 worktree state isn't committed.

Test count: 26/26 daemon tests green (was 17). Common: 13/13. Clippy + fmt clean.
2026-04-27 06:44:07 -07:00

84 lines
2.8 KiB
Rust

use anyhow::{Context, Result};
use std::sync::Arc;
use tracing::{info, warn};
mod dbus;
mod polkit;
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 state = Arc::new(state::AppState::open(cfg)?);
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(())
}