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>
This commit is contained in:
michael
2026-04-27 09:51:52 -07:00
parent 56ba4dd924
commit 982b9403d0
3 changed files with 59 additions and 5 deletions

View File

@@ -212,10 +212,33 @@ pub(crate) async fn dispatch(json: bool, cmd: super::Cmd) -> Result<()> {
}
super::Cmd::Recovery {
cmd: super::RecoveryCmd::List { user: _ },
cmd: super::RecoveryCmd::List,
} => {
if !json {
println!("(ListRecovery lands in Phase 12)");
let entries = d.list_recovery_codes().await?;
if json {
println!("{}", serde_json::to_string_pretty(&entries)?);
} else if entries.is_empty() {
println!("(no active recovery codes)");
} else {
for e in &entries {
println!("{:<24} expires_unix={}", e.user, e.expires_unix);
}
}
}
super::Cmd::Recovery {
cmd: super::RecoveryCmd::Revoke { user },
} => {
let removed = d.revoke_recovery_code(&user).await?;
if removed {
if !json {
println!("revoked recovery code for {user}");
}
} else {
if !json {
eprintln!("no recovery code for {user}");
}
std::process::exit(1);
}
}
}