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:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use authforge_common::policy::Policy;
|
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 BUS_NAME: &str = "io.dangerousthings.AuthForge";
|
||||||
const OBJECT_PATH: &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<String> {
|
pub async fn generate_recovery_code(&self, user: &str) -> Result<String> {
|
||||||
Ok(self.proxy.call("GenerateRecoveryCode", &(user,)).await?)
|
Ok(self.proxy.call("GenerateRecoveryCode", &(user,)).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list_recovery_codes(&self) -> Result<Vec<RecoveryCodeSummary>> {
|
||||||
|
Ok(self.proxy.call("ListRecoveryCodes", &()).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn revoke_recovery_code(&self, user: &str) -> Result<bool> {
|
||||||
|
Ok(self.proxy.call("RevokeRecoveryCode", &(user,)).await?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,10 +212,33 @@ pub(crate) async fn dispatch(json: bool, cmd: super::Cmd) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super::Cmd::Recovery {
|
super::Cmd::Recovery {
|
||||||
cmd: super::RecoveryCmd::List { user: _ },
|
cmd: super::RecoveryCmd::List,
|
||||||
} => {
|
} => {
|
||||||
if !json {
|
let entries = d.list_recovery_codes().await?;
|
||||||
println!("(ListRecovery lands in Phase 12)");
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ enum PendingCmd {
|
|||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum RecoveryCmd {
|
enum RecoveryCmd {
|
||||||
Generate { user: String },
|
Generate { user: String },
|
||||||
List { user: String },
|
List,
|
||||||
|
Revoke { user: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -174,4 +175,26 @@ mod tests {
|
|||||||
let c = Cli::try_parse_from(["authforgectl", "--json", "status"]).unwrap();
|
let c = Cli::try_parse_from(["authforgectl", "--json", "status"]).unwrap();
|
||||||
assert!(c.json);
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user