Commit Graph

121 Commits

Author SHA1 Message Date
michael
789ff2b38e docs: refresh README status — Phases 0-12 + 15/16 done; 13/14/17/18 remain 2026-04-27 13:04:42 -07:00
michael
22653ff4ff docs(pam): clarify safe_user contract — pre-filter, not authoritative boundary
x..y is correctly NOT rejected by the slash/dot/empty/NUL guard; the
recipe now documents this as an intentional regression test against
over-rejection. Frames canonicalize-then-verify as Phase 17 follow-up
hardening.
2026-04-27 13:00:11 -07:00
michael
ef70c465b8 docs(pam): correct journalctl recipe — tag is caller-set, grep is universal 2026-04-27 12:57:26 -07:00
michael
99cde28bd2 Merge branch 'feature/phase-11-totp' 2026-04-27 12:22:44 -07:00
michael
974514038f style(daemon): rustfmt reorder TotpEnrollment import alphabetically 2026-04-27 12:20:58 -07:00
michael
0d99498c26 style(gui): rustfmt collapse QR QrCode::new line in totp_page 2026-04-27 12:20:47 -07:00
michael
4b6b95c350 docs: phase 11 closeout notes 2026-04-27 12:15:33 -07:00
michael
334fa5e20b feat(cli): authforgectl totp enroll/status/revoke 2026-04-27 12:12:02 -07:00
michael
8060364973 feat(gui): TOTP tab with QR code modal + revoke (feature-gated) 2026-04-27 12:10:54 -07:00
michael
85c8f29b68 feat(daemon): render pam_google_authenticator.so when TOTP-required stack present 2026-04-27 12:06:05 -07:00
michael
8acf3f8f45 feat(daemon): EnrollTotp + IsTotpEnrolled + RevokeTotp D-Bus methods 2026-04-27 12:06:05 -07:00
michael
5fa81e5757 feat(daemon): wire TotpStore into AppState behind feature gate 2026-04-27 12:06:05 -07:00
michael
c56ed0c9a1 feat(daemon): TotpStore with atomic 0600 writes in pam_google_authenticator format
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:06:05 -07:00
michael
e753a33ee4 feat(daemon): TOTP secret generation + base32 + otpauth URI (feature-gated)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:06:05 -07:00
michael
30e36ccf5d chore: add totp feature gate (default-on) + data-encoding + qrcode deps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:06:05 -07:00
michael
c64068c4b5 Merge branch 'feature/phase-10-firstrun' 2026-04-27 12:05:18 -07:00
michael
f2e113e403 Merge branch 'feature/phase-9-and-12-gui' 2026-04-27 12:03:49 -07:00
michael
0dd4aaf082 docs(gui): VM smoke recipe for the first-run modal 2026-04-27 11:28:33 -07:00
michael
85dd0c7111 docs: phase 10 closeout notes 2026-04-27 11:28:12 -07:00
michael
611174859c feat(gui): firstrun modal shows Skip button on daemon-unreachable 2026-04-27 11:27:31 -07:00
michael
9021ff0b3c feat(pkg): autostart .desktop entry running authforge --first-run on GNOME login
GNOME-only autostart entry installed to /etc/xdg/autostart/. Uses
X-GNOME-Autostart-Phase=Initialization so it runs before user
autostarts but after GDM hands the session off. NoDisplay=true keeps
it out of the apps menu — autostart-only.

Plan deviation: project uses debian/rules for installation (no
debian/authforge-gui.install file exists), so the install line goes
there instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:23:28 -07:00
michael
cef468ae7e feat(gui): first-run modal with enrollment + 60s idle watchdog
Replace the prep-lane stub with the real Flow C: a fullscreen,
undecorated adw::ApplicationWindow that queries the daemon's pending
flag for the current user, presents an AdwStatusPage "Get started"
button when a flag is set, runs the Phase 8 enrollment dialog, calls
ClearPendingFlag on success, and exits. A 60s idle watchdog (5s tick)
falls back to gnome-session-quit --logout. EventControllerKey and
GestureClick poke the WatchdogState on activity; missing pending flag
short-circuits to immediate exit so the autostart cost is one D-Bus
call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:22:40 -07:00
michael
3b519578c5 feat(gui): WatchdogState pure-logic helper for first-run modal
Convert gui/src/firstrun.rs to a module dir with watchdog.rs, the
pure-logic WatchdogState helper that the GTK widget code in Task 2
will poll. Three unit tests cover fresh/expired/poke transitions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:19:46 -07:00
michael
b23df83a2b docs: phase 9 + phase 12 GUI closeout notes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:19:45 -07:00
michael
25eed8ff94 docs(gui): smoke-test recipes for Policy and Recovery tabs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:18:50 -07:00
michael
008dc160bd feat(gui): Recovery tab with list / generate / revoke + one-shot copy dialog
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:18:29 -07:00
michael
e19ffc9c9b feat(gui): advanced expander surfaces non-master PAM stacks in Policy tab
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:16:51 -07:00
michael
ae372df577 feat(gui): PolicyPage subscribes to PolicyChanged for live refresh
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:16:18 -07:00
michael
a28b5c9ebe feat(gui): Policy tab with three master stacks and lockout-violation banner
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:15:50 -07:00
michael
b2771e73fa feat(gui): pure-logic helpers for Policy tab (mode mapping, violation grouping)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:13:53 -07:00
michael
ec22188440 feat(gui): bus client policy + recovery method wrappers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:12:54 -07:00
michael
712c1e9c1d Merge branch 'feature/prep-shell-and-pending' 2026-04-27 10:56:14 -07:00
michael
2ed95d21da style(daemon): rustfmt collapse get_pending_status match arm
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:55:51 -07:00
michael
b8e8368a25 feat(gui): bus::Daemon::{get_pending_status,clear_pending_flag} client wrappers
clear_pending_flag is added alongside get_pending_status because Phase 10
needs both — its first-login modal checks status on startup and clears
the flag on successful enrollment. Bundling here keeps the Phase 10 lane
free of bus.rs edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:52:14 -07:00
michael
1976971bb5 feat(daemon): GetPendingStatus D-Bus method + 2 integration tests
Drops the #[allow(dead_code)] on AppState::get_pending_status — it's now
called from the D-Bus dispatch. Two new p2p tests cover the absent and
the round-trip cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:51:25 -07:00
michael
86e3ffe8d5 feat(daemon): AppState::get_pending_status returns wire-friendly bool+flag
has_pending stays #[cfg(test)] because production GUI reads via the new
get_pending_status (one round-trip carries both presence and flag);
production PAM module reads the file directly. Method gated with
#[allow(dead_code)] until Task 5 wires D-Bus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:49:53 -07:00
michael
e1542d6d1f feat(common): PendingStatus wire type + Default impl on PendingFlag
PendingStatus carries an explicit 'present' bool next to a PendingFlag
because zvariant doesn't support Option<T>. Two new serde tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:47:01 -07:00
michael
8d19be3e58 feat(gui): --first-run flag + firstrun::run stub for Phase 10
argv is read manually before adw::Application sees it (GTK's option parser
rejects unknown long flags), then the filtered list is passed to
app.run_with_args. Stub exits cleanly with a stderr breadcrumb so a
developer who passes --first-run before Phase 10 lands doesn't get a
stuck process.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:45:52 -07:00
michael
a927d96630 refactor(gui): extract AppContext for shared page handles
KeysPage::new now takes a single AppContext that owns parent_window,
toast_overlay, and the shared daemon Rc<RefCell<Option<Daemon>>>. Phases
9, 10, 11, and 12 GUI tab will all consume this struct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:42:11 -07:00
michael
d20643cde5 docs(plan): coordinator + 3 step-level plans for parallel Phase 9/10/11 fan-out
Ships four documents:
  * 2026-04-27-parallel-fanout-9-10-11.md — coordinator. File-conflict map,
    prep-lane scope rationale, post-prep parallel-safety guarantee, merge
    order rule, and risks.
  * 2026-04-27-prep-shell-and-pending.md — small ~1hr prep lane: extract
    AppContext, add --first-run flag scaffold (with stub firstrun::run),
    add daemon GetPendingStatus D-Bus method + GUI client wrapper, new
    PendingStatus wire type. 6 tasks, single agent.
  * 2026-04-27-phase-10-firstrun.md — first-login flow. Fullscreen modal,
    60s idle watchdog (TDD'd as pure WatchdogState), enrollment via Phase
    8 enroll_dialog, ClearPendingFlag on success, gnome-session-quit on
    idle, autostart .desktop entry, debian install. 6 tasks.
  * 2026-04-27-phase-11-totp.md — TOTP support behind default-on Cargo
    feature. Daemon-side: 160-bit secret + base32 + otpauth URI + atomic
    0600 writes in pam_google_authenticator format. PAM profile renderer
    extension. D-Bus surface. CLI subcommand. GUI tab with QR modal.
    Deviation flagged: TOTP recovery codes reuse the Phase 12 recovery
    flow rather than introducing a parallel hashed-recovery file format.
    9 tasks.

Execution model: prep lane first (single agent ~1hr), then dispatch three
subagents in parallel worktrees for the three follow-on lanes. Per the
conflict map, the lanes are file-disjoint after prep merges; merge order
is any-order.

If all four lanes land cleanly, the roadmap jumps from 13/19 to 17/19
phases code-complete in a single session, leaving Phase 13 (deb finalization),
14 (PPA + smoke), 17 (integration tests), 18 (user docs), and the v1.0
release tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:28:23 -07:00
michael
b624f005b2 docs(roadmap): refresh post Phase 8 + Phase 12 backend merges
* Phase 9/10/11 status rows: dependencies satisfied, mark "open now" with
  pointers to step-level plans where they exist.
* Phase 12 row: GUI tab is folded into the Phase 9 lane plan (not its own).
* ASCII dependency diagram: collapse the just-landed Phase-8/Phase-12-backend
  layer; show the three open lanes (9+12-GUI, 11 TOTP, 10 first-login)
  fanning into Phase 13.
* "Parallel-safe right now" heading bumped to post-Phase-8+12-backend.
  Table swaps the now-stale Recovery and post-Phase-8 GUI rows for the
  three actually-open lanes, with file footprints + plan links.
* Sequential tail paragraph names the six Code-complete phases (1, 3, 4,
  6, 8, 12) whose deferred gates Phase 14 must clear.
* Phase 12 closeout intro names the merge commit and corrects the now-stale
  "waiting on Phase 8" wording.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:09:22 -07:00
michael
4ec69119f6 Merge branch 'feature/phase-12-recovery-backend'
# Conflicts:
#	docs/plans/2026-04-26-authforge-implementation.md
2026-04-27 10:05:59 -07:00
michael
91731150d5 docs: phase 12 backend closeout notes
Flips Phase 12 status to  Code complete; bumps progress to 12/19 (63%).
Closeout block summarizes the 8 implementation tasks, lists all new
artifacts, calls out the 80-test count (was 60), and flags the Phase 14
deferred verifications (libargon2-dev compile + pamtester smoke).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:53:09 -07:00
michael
982b9403d0 feat(cli): authforgectl recovery list and revoke
* RecoveryCmd::List no longer takes a user — lists all active codes
  across users, matching the daemon's ListRecoveryCodes signature.
* RecoveryCmd::Revoke <user> calls RevokeRecoveryCode; exits 1 with
  a stderr message when the user has no active code.
* bus.rs gains list_recovery_codes() / revoke_recovery_code(user).
* Two new clap-parser tests cover the new subcommand shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:51:52 -07:00
michael
56ba4dd924 feat(pam): recovery-code mode with Argon2id verify and pending re-enroll handoff
C-side:
* New mode=recovery argv branch in pam_sm_authenticate. Reads the two-line
  /var/lib/authforge/recovery/<user> file, argon2_verify against PAM_AUTHTOK,
  on match unlinks the file (one-shot) and writes pending(re_enroll=true).
  Always returns PAM_IGNORE on failure paths so a missing/wrong code never
  blocks normal auth.
* Makefile links -largon2 alongside -lpam.

Daemon-side:
* policy_apply::render_profile renders the recovery line first in the auth
  stack with [success=done default=ignore] — successful recovery short-
  circuits the rest, missing/wrong code falls through.
* New policy_apply test asserts the recovery line precedes the default
  backstop.

Doc:
* pam/TESTING.md adds libargon2-dev to the build prereqs and a new
  Smoke test 4 walking through the manual recovery-code flow.

C compile gate (make -C pam) requires libargon2-dev — flagged as a
deferred verification step until a host with the dev package is available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:49:44 -07:00
michael
a420aa5f75 feat(daemon): real GenerateRecoveryCode + ListRecoveryCodes + RevokeRecoveryCode
Replace the random-number stub at dbus.rs:132 with state.issue_recovery,
add ListRecoveryCodes (returns Vec<RecoveryCodeSummary>) and RevokeRecoveryCode.
New polkit actions list-recovery / revoke-recovery (auth_admin_keep). Adds
4 D-Bus integration tests; the previously-stub generate-code test now
exercises the real Argon2id-backed write path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:39:16 -07:00
michael
42482f2a44 docs: mark Phase 8 done in roadmap + closeout notes 2026-04-27 09:37:22 -07:00
michael
e9efde9077 feat(daemon): wire RecoveryStore into AppState with AUTHFORGE_RECOVERY_DIR override
StorageConfig gains a recovery_dir field; from_env_or_defaults reads
AUTHFORGE_RECOVERY_DIR (default /var/lib/authforge/recovery). AppState
exposes issue/list/revoke methods that Task 6 wires through D-Bus. Test
fixtures in state.rs and dbus.rs updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:35:06 -07:00
michael
517b015996 feat(daemon): RecoveryStore with atomic 0600 writes and one-shot verify-and-consume
Two-line file format (expires_unix\nargon2id-PHC) keeps the C PAM module
parser trivial — no json-c link needed. Atomic temp+rename means a
concurrent reader never sees a half-written file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:32:40 -07:00
michael
943d9b01ae Merge branch 'feature/phase-8-gui-keys'
Phase 8: GUI Keys tab. Lists current user's enrolled FIDO2 credentials,
enrolls a new key with a touch-prompt modal driven by the daemon's
EnrollmentFailed signal (success comes from the call return), removes
credentials per row, degrades gracefully to a Retry banner when the
daemon is unreachable, and surfaces transient errors via adw::Toast.

8 tasks, 4 unit tests (error classifier).
2026-04-27 09:32:37 -07:00