Commit Graph

113 Commits

Author SHA1 Message Date
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
michael
27dec5ab4a docs(gui): smoke-test recipe and CI gate 2026-04-27 09:26:17 -07:00
michael
14b473a48a feat(gui): wire adw::ToastOverlay for error surfacing 2026-04-27 09:25:50 -07:00
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
michael
3fbd872a9c feat(gui): TouchDialog modal racing EnrollOwn against EnrollmentFailed signal
Adds enroll_dialog::present which opens an adw::AlertDialog with a spinner
and races the EnrollOwn call against the EnrollmentFailed signal stream:
- Call returns Ok(_cred) -> close dialog, fire on_success refresh callback.
- Call returns Err -> swap dialog body to the user-message form.
- Signal arrives first -> swap to the signal payload (faster than waiting
  for the call's typed Err to traverse the bus).

KeysPage::start_enroll wires the activated row to present(), passing the
parent window for modal anchoring and a refresh closure as on_success.

Plan deviation: AlertDialog is libadwaita v1_5-gated, so gui/Cargo.toml
enables that feature. Targets Ubuntu 24.04+ (libadwaita 1.5).
2026-04-27 09:24:50 -07:00
michael
402addca27 chore(daemon): add argon2 0.5 for recovery code hashing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:23:20 -07:00
michael
eb208579e3 refactor(daemon): centralize username path-segment sanitizer
Extract the inline path-traversal check from PendingStore into a shared
storage::safe_user::join_user_segment helper. RecoveryStore (next) reuses it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:22:23 -07:00
michael
f11047fbcb feat(gui): list enrolled credentials with per-row remove + enroll button 2026-04-27 09:21:45 -07:00
michael
5d6b7ceeb0 feat(gui): KeysPage scaffold + tokio runtime in main, gtk::Box layout
KeysPage::new spawns the connect-and-render flow on glib::MainContext::
spawn_local. On success it shows a placeholder PreferencesPage (Task 5
fills in the list); on failure it shows StatusPage with a Retry button
that re-runs the connect attempt.

main.rs installs the multi-thread tokio runtime guard before app.run()
so zbus's tokio futures execute correctly when polled by glib.

Layout deviation from plan: ToolbarView is libadwaita v1_4-gated; the
project sticks with gtk::Box vertical for portability (commit c6a5e94
established this pattern).
2026-04-27 09:20:31 -07:00