Skip to content

Commit b203e13

Browse files
authored
Merge pull request #45 from auths-dev/fn-42
refactor: simplify CLI and SDK interfaces
2 parents 0dd36c6 + 25402ca commit b203e13

33 files changed

Lines changed: 470 additions & 240 deletions

crates/auths-cli/src/bin/sign.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ fn run_sign(args: &Args) -> Result<()> {
281281
mod tests {
282282
use super::*;
283283
use auths_core::crypto::ssh::construct_sshsig_signed_data;
284-
use zeroize::Zeroizing;
284+
use auths_crypto::Pkcs8Der;
285285

286286
#[test]
287287
fn test_args_accepts_o_flag() {
@@ -458,9 +458,9 @@ mod tests {
458458
let rng = SystemRandom::new();
459459
let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng)
460460
.expect("ring must generate a valid PKCS#8 document");
461-
let pkcs8_bytes = Zeroizing::new(pkcs8_doc.as_ref().to_vec());
461+
let pkcs8 = Pkcs8Der::new(pkcs8_doc.as_ref());
462462

463-
let result = extract_seed_from_pkcs8(&pkcs8_bytes);
463+
let result = extract_seed_from_pkcs8(&pkcs8);
464464
assert!(
465465
result.is_ok(),
466466
"extract_seed_from_pkcs8 must succeed on a ring-generated key, got: {:?}",
@@ -472,8 +472,7 @@ mod tests {
472472

473473
let derived = Ed25519KeyPair::from_seed_unchecked(seed.as_bytes())
474474
.expect("extracted seed must be valid");
475-
let original =
476-
Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).expect("original key must parse");
475+
let original = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).expect("original key must parse");
477476
assert_eq!(
478477
derived.public_key().as_ref(),
479478
original.public_key().as_ref(),
@@ -485,7 +484,7 @@ mod tests {
485484
fn test_extract_seed_from_pkcs8_rejects_invalid_input() {
486485
use auths_core::crypto::ssh::extract_seed_from_pkcs8;
487486

488-
let bad_input = Zeroizing::new(vec![0u8; 50]);
487+
let bad_input = Pkcs8Der::new(vec![0u8; 50]);
489488
let result = extract_seed_from_pkcs8(&bad_input);
490489
assert!(result.is_err(), "must reject non-PKCS#8 input");
491490
}

crates/auths-cli/src/cli.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::commands::sign::SignCommand;
2626
use crate::commands::status::StatusCommand;
2727
use crate::commands::trust::TrustCommand;
2828
use crate::commands::unified_verify::UnifiedVerifyCommand;
29+
use crate::commands::whoami::WhoamiCommand;
2930
use crate::commands::witness::WitnessCommand;
3031
use crate::config::OutputFormat;
3132

@@ -44,7 +45,7 @@ fn cli_styles() -> Styles {
4445
#[command(
4546
name = "auths",
4647
about = "auths \u{2014} cryptographic identity for developers",
47-
long_about = "Commands:\n init Set up your cryptographic identity and Git signing\n sign Sign a Git commit or artifact\n verify Verify a signed commit or attestation\n status Show identity and signing status\n\nMore commands (run with --help for details):\n auths device, auths id, auths key, auths policy, auths emergency, ...",
48+
long_about = "auths \u{2014} cryptographic identity for developers\n\nCore commands:\n init Set up your cryptographic identity and Git signing\n sign Sign a Git commit or artifact\n verify Verify a signed commit or attestation\n status Show identity and signing status\n\nMore commands:\n id, device, key, approval, artifact, policy, git, trust, org,\n audit, agent, witness, scim, config, emergency\n\nRun `auths <command> --help` for details on any command.",
4849
version,
4950
styles = cli_styles()
5051
)]
@@ -97,24 +98,40 @@ pub enum RootCommand {
9798
Sign(SignCommand),
9899
Verify(UnifiedVerifyCommand),
99100
Status(StatusCommand),
101+
Whoami(WhoamiCommand),
100102
Tutorial(LearnCommand),
101103
Doctor(DoctorCommand),
102104
Completions(CompletionsCommand),
105+
#[command(hide = true)]
103106
Emergency(EmergencyCommand),
104107

108+
#[command(hide = true)]
105109
Id(IdCommand),
110+
#[command(hide = true)]
106111
Device(DeviceCommand),
112+
#[command(hide = true)]
107113
Key(KeyCommand),
114+
#[command(hide = true)]
108115
Approval(ApprovalCommand),
116+
#[command(hide = true)]
109117
Artifact(ArtifactCommand),
118+
#[command(hide = true)]
110119
Policy(PolicyCommand),
120+
#[command(hide = true)]
111121
Git(GitCommand),
122+
#[command(hide = true)]
112123
Trust(TrustCommand),
124+
#[command(hide = true)]
113125
Org(OrgCommand),
126+
#[command(hide = true)]
114127
Audit(AuditCommand),
128+
#[command(hide = true)]
115129
Agent(AgentCommand),
130+
#[command(hide = true)]
116131
Witness(WitnessCommand),
132+
#[command(hide = true)]
117133
Scim(ScimCommand),
134+
#[command(hide = true)]
118135
Config(ConfigCommand),
119136

120137
#[command(hide = true)]

crates/auths-cli/src/commands/device/authorization.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ pub enum DeviceSubcommand {
136136

137137
#[arg(long, help = "Optional note explaining the revocation.")]
138138
note: Option<String>,
139+
140+
#[arg(long, help = "Preview actions without making changes.")]
141+
dry_run: bool,
139142
},
140143

141144
/// Resolve a device DID to its controller identity DID.
@@ -276,7 +279,12 @@ pub fn handle_device(
276279
device_did,
277280
identity_key_alias,
278281
note,
282+
dry_run,
279283
} => {
284+
if dry_run {
285+
return display_dry_run_revoke(&device_did, &identity_key_alias);
286+
}
287+
280288
let ctx = build_auths_context(
281289
&repo_path,
282290
env_config,
@@ -325,6 +333,38 @@ fn display_link_result(
325333
Ok(())
326334
}
327335

336+
fn display_dry_run_revoke(device_did: &str, identity_key_alias: &str) -> Result<()> {
337+
if is_json_mode() {
338+
JsonResponse::success(
339+
"device revoke",
340+
&serde_json::json!({
341+
"dry_run": true,
342+
"device_did": device_did,
343+
"identity_key_alias": identity_key_alias,
344+
"actions": [
345+
"Revoke device authorization",
346+
"Create signed revocation attestation",
347+
"Store revocation in Git repository"
348+
]
349+
}),
350+
)
351+
.print()
352+
.map_err(|e| anyhow!("{e}"))
353+
} else {
354+
let out = crate::ux::format::Output::new();
355+
out.print_info("Dry run mode — no changes will be made");
356+
out.newline();
357+
out.println("Would perform the following actions:");
358+
out.println(&format!(
359+
" 1. Revoke device authorization for {}",
360+
device_did
361+
));
362+
out.println(" 2. Create signed revocation attestation");
363+
out.println(" 3. Store revocation in Git repository");
364+
Ok(())
365+
}
366+
}
367+
328368
fn display_revoke_result(device_did: &str, repo_path: &Path) -> Result<()> {
329369
let identity_storage = RegistryIdentityStorage::new(repo_path.to_path_buf());
330370
let identity: ManagedIdentity = identity_storage

crates/auths-cli/src/commands/device/pair/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub struct PairCommand {
3434
pub registry: Option<String>,
3535

3636
/// Don't display QR code (only show short code)
37-
#[clap(long)]
37+
#[clap(long, hide_short_help = true)]
3838
pub no_qr: bool,
3939

4040
/// Custom timeout in seconds for the pairing session (default: 300 = 5 minutes)
@@ -47,16 +47,21 @@ pub struct PairCommand {
4747
pub timeout: u64,
4848

4949
/// Skip registry server (offline mode, for testing)
50-
#[clap(long)]
50+
#[clap(long, hide_short_help = true)]
5151
pub offline: bool,
5252

5353
/// Capabilities to grant the paired device (comma-separated)
54-
#[clap(long, value_delimiter = ',', default_value = "sign_commit")]
54+
#[clap(
55+
long,
56+
value_delimiter = ',',
57+
default_value = "sign_commit",
58+
hide_short_help = true
59+
)]
5560
pub capabilities: Vec<String>,
5661

5762
/// Disable mDNS advertisement/discovery in LAN mode
5863
#[cfg(feature = "lan-pairing")]
59-
#[clap(long)]
64+
#[clap(long, hide_short_help = true)]
6065
pub no_mdns: bool,
6166
}
6267

crates/auths-cli/src/commands/id/identity.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ pub enum IdSubcommand {
158158
help = "New simple verification threshold count (e.g., 1 for 1-of-N)."
159159
)]
160160
witness_threshold: Option<u64>,
161+
162+
/// Preview actions without making changes.
163+
#[arg(long)]
164+
dry_run: bool,
161165
},
162166

163167
/// Export an identity bundle for stateless CI/CD verification.
@@ -196,6 +200,50 @@ pub enum IdSubcommand {
196200
Migrate(super::migrate::MigrateCommand),
197201
}
198202

203+
fn display_dry_run_rotate(
204+
repo_path: &std::path::Path,
205+
current_alias: Option<&str>,
206+
next_alias: Option<&str>,
207+
) -> Result<()> {
208+
if is_json_mode() {
209+
JsonResponse::success(
210+
"id rotate",
211+
&serde_json::json!({
212+
"dry_run": true,
213+
"repo_path": repo_path.display().to_string(),
214+
"current_key_alias": current_alias,
215+
"next_key_alias": next_alias,
216+
"actions": [
217+
"Generate new Ed25519 keypair",
218+
"Create rotation event in KERI event log",
219+
"Update key alias mappings",
220+
"All devices will need to re-authorize"
221+
]
222+
}),
223+
)
224+
.print()
225+
.map_err(|e| anyhow!("{e}"))
226+
} else {
227+
let out = crate::ux::format::Output::new();
228+
out.print_info("Dry run mode — no changes will be made");
229+
out.newline();
230+
out.println(&format!(" Repository: {:?}", repo_path));
231+
if let Some(alias) = current_alias {
232+
out.println(&format!(" Current Key Alias: {}", alias));
233+
}
234+
if let Some(alias) = next_alias {
235+
out.println(&format!(" New Key Alias: {}", alias));
236+
}
237+
out.newline();
238+
out.println("Would perform the following actions:");
239+
out.println(" 1. Generate new Ed25519 keypair");
240+
out.println(" 2. Create rotation event in KERI event log");
241+
out.println(" 3. Update key alias mappings");
242+
out.println(" 4. All devices will need to re-authorize");
243+
Ok(())
244+
}
245+
}
246+
199247
// --- Command Handler ---
200248

201249
/// Handles the `id` subcommand, accepting the specific subcommand details
@@ -425,9 +473,18 @@ pub fn handle_id(
425473
add_witness,
426474
remove_witness,
427475
witness_threshold,
476+
dry_run,
428477
} => {
429478
let identity_key_alias = alias.or(current_key_alias);
430479

480+
if dry_run {
481+
return display_dry_run_rotate(
482+
&repo_path,
483+
identity_key_alias.as_deref(),
484+
next_key_alias.as_deref(),
485+
);
486+
}
487+
431488
println!("🔄 Rotating KERI identity keys...");
432489
println!(" Using Repository: {:?}", repo_path);
433490
if let Some(ref a) = identity_key_alias {

crates/auths-cli/src/commands/init.rs

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::factories::storage::build_auths_context;
3333

3434
use super::init_helpers::{
3535
check_git_version, detect_ci_environment, get_auths_repo_path, offer_shell_completions,
36-
select_agent_capabilities, short_did, write_allowed_signers,
36+
select_agent_capabilities, write_allowed_signers,
3737
};
3838
use crate::config::CliConfig;
3939
use crate::ux::format::Output;
@@ -179,14 +179,8 @@ pub fn handle_init(cmd: InitCommand, ctx: &CliConfig) -> Result<()> {
179179
_ => unreachable!(),
180180
};
181181

182-
out.print_success(&format!(
183-
"Identity ready: {}",
184-
short_did(&result.identity_did)
185-
));
186-
out.print_success(&format!(
187-
"Device linked: {}",
188-
short_did(result.device_did.as_str())
189-
));
182+
out.print_success(&format!("Identity ready: {}", &result.identity_did));
183+
out.print_success(&format!("Device linked: {}", result.device_did.as_str()));
190184
out.newline();
191185

192186
// Post-execute: platform verification (interactive CLI concern)
@@ -452,7 +446,7 @@ fn prompt_for_conflict_policy(
452446
if let Ok(existing) = identity_storage.load_identity() {
453447
out.println(&format!(
454448
" Found existing identity: {}",
455-
out.info(&short_did(existing.controller_did.as_str()))
449+
out.info(existing.controller_did.as_str())
456450
));
457451

458452
if !interactive {
@@ -627,10 +621,7 @@ fn display_developer_result(
627621
out.newline();
628622
out.print_heading("You are on the Web of Trust!");
629623
out.newline();
630-
out.println(&format!(
631-
" Identity: {}",
632-
out.info(&short_did(&result.identity_did))
633-
));
624+
out.println(&format!(" Identity: {}", out.info(&result.identity_did)));
634625
out.println(&format!(" Key alias: {}", out.info(&result.key_alias)));
635626
if let Some(registry) = registered {
636627
out.println(&format!(" Registry: {}", out.info(registry)));
@@ -653,7 +644,7 @@ fn display_ci_result(
653644
result: &auths_sdk::result::CiIdentityResult,
654645
ci_vendor: Option<&str>,
655646
) {
656-
out.print_success(&format!("CI identity: {}", short_did(&result.identity_did)));
647+
out.print_success(&format!("CI identity: {}", &result.identity_did));
657648
out.newline();
658649

659650
out.print_heading("Add these to your CI secrets:");
@@ -676,10 +667,7 @@ fn display_ci_result(
676667
fn display_agent_result(out: &Output, result: &auths_sdk::result::AgentIdentityResult) {
677668
out.print_heading("Agent Setup Complete!");
678669
out.newline();
679-
out.println(&format!(
680-
" Identity: {}",
681-
out.info(&short_did(&result.agent_did))
682-
));
670+
out.println(&format!(" Identity: {}", out.info(&result.agent_did)));
683671
let cap_display: Vec<String> = result.capabilities.iter().map(|c| c.to_string()).collect();
684672
out.println(&format!(" Capabilities: {}", cap_display.join(", ")));
685673
out.newline();

crates/auths-cli/src/commands/init_helpers.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@ pub(crate) fn get_auths_repo_path() -> Result<PathBuf> {
1717
auths_core::paths::auths_home().map_err(|e| anyhow!(e))
1818
}
1919

20-
pub(crate) fn short_did(did: &str) -> String {
21-
if did.len() <= 24 {
22-
did.to_string()
23-
} else {
24-
format!("{}...{}", &did[..16], &did[did.len() - 8..])
25-
}
26-
}
27-
2820
pub(crate) fn check_git_version(out: &Output) -> Result<()> {
2921
let output = Command::new("git")
3022
.arg("--version")
@@ -340,15 +332,6 @@ mod tests {
340332
assert_eq!(parse_git_version("git version 2.30").unwrap(), (2, 30, 0));
341333
}
342334

343-
#[test]
344-
fn test_short_did() {
345-
assert_eq!(short_did("did:key:z123"), "did:key:z123");
346-
assert_eq!(
347-
short_did("did:keri:EAbcdefghijklmnopqrstuvwxyz123456789"),
348-
"did:keri:EAbcdef...23456789"
349-
);
350-
}
351-
352335
#[test]
353336
fn test_min_git_version() {
354337
assert!(MIN_GIT_VERSION <= (2, 34, 0));

crates/auths-cli/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ pub mod unified_verify;
3131
pub mod utils;
3232
pub mod verify_commit;
3333
pub mod verify_helpers;
34+
pub mod whoami;
3435
pub mod witness;

0 commit comments

Comments
 (0)