diff --git a/cli/src/bus.rs b/cli/src/bus.rs index 318fcc0..b831579 100644 --- a/cli/src/bus.rs +++ b/cli/src/bus.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use authforge_common::policy::Policy; -use authforge_common::types::{Credential, PendingFlag, PolicyApplyResult}; +use authforge_common::types::{Credential, PendingFlag, PolicyApplyResult, RecoveryCodeSummary}; const BUS_NAME: &str = "io.dangerousthings.AuthForge"; const OBJECT_PATH: &str = "/io/dangerousthings/AuthForge"; @@ -56,4 +56,12 @@ impl Daemon { pub async fn generate_recovery_code(&self, user: &str) -> Result { Ok(self.proxy.call("GenerateRecoveryCode", &(user,)).await?) } + + pub async fn list_recovery_codes(&self) -> Result> { + Ok(self.proxy.call("ListRecoveryCodes", &()).await?) + } + + pub async fn revoke_recovery_code(&self, user: &str) -> Result { + Ok(self.proxy.call("RevokeRecoveryCode", &(user,)).await?) + } } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 9e589fc..492f1bf 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -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); } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 82d02ce..5c31a1a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -95,7 +95,8 @@ enum PendingCmd { #[derive(Subcommand)] enum RecoveryCmd { Generate { user: String }, - List { user: String }, + List, + Revoke { user: String }, } #[tokio::main] @@ -174,4 +175,26 @@ mod tests { let c = Cli::try_parse_from(["authforgectl", "--json", "status"]).unwrap(); assert!(c.json); } + + #[test] + fn parse_recovery_list_takes_no_args() { + let c = Cli::try_parse_from(["authforgectl", "recovery", "list"]).unwrap(); + assert!(matches!( + c.cmd, + Cmd::Recovery { + cmd: RecoveryCmd::List + } + )); + } + + #[test] + fn parse_recovery_revoke_with_user() { + let c = Cli::try_parse_from(["authforgectl", "recovery", "revoke", "alice"]).unwrap(); + match c.cmd { + Cmd::Recovery { + cmd: RecoveryCmd::Revoke { user }, + } => assert_eq!(user, "alice"), + _ => panic!("wrong subcommand"), + } + } }