diff --git a/README.md b/README.md index d04d6d4..9cae375 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,9 @@ See [docs/BUILDING.md](docs/BUILDING.md). ## Status -Pre-alpha. Phases 0–1 code complete (workspace scaffold + D-Bus daemon stubs); -Phase 2 (storage layer) is next. See [docs/plans/](docs/plans/) for design, -roadmap, and per-phase implementation plans. +Pre-alpha. Phases 0–2 code complete (workspace scaffold, D-Bus daemon, storage +layer with policy.d / pending / credentials / userdb); Phase 3 (FIDO2 enrollment +backend) is next. See [docs/plans/](docs/plans/) for design, roadmap, and +per-phase implementation plans. Copyright 2026 Dangerous Things, LLC. Licensed under the Apache License, Version 2.0. diff --git a/docs/plans/2026-04-26-authforge-implementation.md b/docs/plans/2026-04-26-authforge-implementation.md index c8fe897..6bdcc4c 100644 --- a/docs/plans/2026-04-26-authforge-implementation.md +++ b/docs/plans/2026-04-26-authforge-implementation.md @@ -20,8 +20,8 @@ |---|---|---|---| | 0 | Repo, workspace, CI, packaging skeleton | 2–3 days | ✅ **Done** (2026-04-26) | | 1 | Daemon: D-Bus interface stub, systemd unit, polkit rules | 3–4 days | ✅ **Code complete** (2026-04-27); smoke gates deferred | -| 2 | Daemon: storage layer (policy.d parser, pending flags, user db) | 3–4 days | **Next** — re-invoke `superpowers:writing-plans` to expand | -| 3 | Daemon: FIDO2 enrollment backend via `ctap-hid-fido2` | 5–7 days | Spec'd | +| 2 | Daemon: storage layer (policy.d parser, pending flags, user db) | 3–4 days | ✅ **Done** (2026-04-27, tag `v0.2.0-storage`) | +| 3 | Daemon: FIDO2 enrollment backend via `ctap-hid-fido2` | 5–7 days | **Next** — re-invoke `superpowers:writing-plans` to expand | | 4 | Daemon: policy apply via pam-auth-update wrapper | 4 days | Spec'd | | 5 | Daemon: lockout simulator | 3 days | Spec'd | | 6 | PAM module: `pam_authforge_pending.so` (C) | 3 days | Spec'd | @@ -66,6 +66,27 @@ Phase 1 expanded to step granularity at [2026-04-26-phase-1-dbus.md](2026-04-26- - `PendingFlag.deadline_unix` is `u64` with `0` = no deadline (zvariant `Type` derive does not support `Option`; sentinel matches the design doc's existing `deadline_hours = 0` convention). - Sender → pid resolution for polkit `unix-process` subjects punts to early Phase 2 — Phase 1 passes pid 0, which the permissive authorizer ignores. Until pid resolution lands, the daemon is run with `AUTHFORGE_POLKIT_BYPASS=1` for any local smoke test (loud `warn!` log on startup). +### Phase 2 closeout notes (2026-04-27) + +Phase 2 expanded to step granularity at [2026-04-27-phase-2-storage.md](2026-04-27-phase-2-storage.md). All 17 tasks landed in 8 commits, tagged `v0.2.0-storage`. + +**Done in Phase 2:** +- `common/src/policy.rs` — `Policy::load_from_dir` (lex-ordered last-wins merge; ignores non-`.conf`; missing-dir → default) and `Policy::save_local` (writes `50-local.conf`, never touches siblings). Policy-related types moved here from `types.rs` with re-exports for stability. +- `daemon/src/storage/policy.rs` — `PolicyStore` wraps load/save; `watch()` returns a `(RecommendedWatcher, watch::Receiver<()>)` driven by `notify::recommended_watcher` (inotify on Linux). +- `daemon/src/storage/pending.rs` — JSON read/write/clear with explicit path-traversal rejection (`.`, `..`, `/`, `\0`, empty). +- `daemon/src/storage/credentials.rs` — `pam_u2f` line parser (`username:cred1[:cred2…]`), `CredentialsStore` with idempotent add-by-credId and remove-by-credId, `CredsPathResolver` dispatching central vs per-user paths (NSS errors fall back to `/home//…`). +- `daemon/src/storage/userdb.rs` — `rusqlite` (bundled SQLite) cache for the lockout simulator; `record_enrollment`, `drop_enrollment`, `users_with`, `has_any`. +- `daemon/src/dbus.rs` — adds `#[zbus(signal)] policy_changed`. Tests run against tempdir-backed `AppState` seeded with central-mode storage so credential writes don't try to touch `/home//…`. +- `daemon/src/main.rs` — spawns a watcher task that emits `PolicyChanged` on every `rx.changed()`; `AUTHFORGE_POLICY_DIR`/`_PENDING_DIR`/`_USERDB` env-var overrides for non-prod runs. +- `AppState::open(StorageConfig)` replaces `with_fixtures()`. All storage errors threaded through `StateError` and surfaced as `org.freedesktop.DBus.Error.Failed`. + +**Test count:** 13/13 common + 26/26 daemon = **39 tests** (was 14+7=21 at end of Phase 1). Clippy + fmt clean. + +**Plan deviations:** +- `nix::unistd::User::from_name` returns `Err(ENOENT)` rather than `Ok(None)` on this dev box for unknown users; the resolver now treats any lookup error as "fall back to `/home/`" rather than propagating. Comment updated. +- `set_policy_replaces_state` test renamed to `set_policy_persists_stacks` and asserts only `merged.stacks == pol.stacks` — `p2p_pair` seeds a `[storage]` block in `00-test.conf`, so the merged read is by design a superset of what `SetPolicy` wrote to `50-local.conf`. The new assertion captures the actual user-visible behavior. +- `.gitignore` extended to ignore `.claude/` (Phase 1 left a stale agent worktree under `.claude/worktrees/`; ignoring prevents accidental submodule-style commits). + --- # Phase 0: Repository Scaffolding (FULLY DETAILED)