auths_cli/commands/did_webs.rs
1//! `auths did-webs` — emit a `did:webs` DID document for an AID.
2//!
3//! `did:webs` anchors a KERI AID into a **web-resolvable** DID document so a
4//! standard DID resolver verifies the identifier without speaking KERI. The
5//! document is derived by replaying the AID's KEL into its current key-state, so
6//! the verification material is exactly the AID's current signing keys — the KEL
7//! stays the source of truth. The document auths emits is byte-compatible with
8//! the ToIP did:webs reference resolver's `didDocument`
9//! (`{id, verificationMethod, service, alsoKnownAs}`). The crypto/wire definition
10//! lives in `auths-keri::did_webs`; this is a thin CLI adapter over it.
11
12use std::path::{Path, PathBuf};
13
14use anyhow::{Result, anyhow};
15use auths_keri::{DidWebsDocument, TrustedKel, parse_kel_json};
16use auths_utils::path::expand_tilde;
17use clap::Parser;
18
19use crate::config::CliConfig;
20
21/// Emit a `did:webs` DID document for an AID (KEL-anchored).
22#[derive(Parser, Debug, Clone)]
23#[command(
24 about = "Emit a did:webs DID document for an AID — resolvable under a standard did:webs resolver",
25 after_help = "Examples:
26 auths did-webs --from-kel kel.json --domain example.com
27 auths did-webs --from-kel kel.json --domain 'example.com%3A3901:dids'"
28)]
29pub struct DidWebsCommand {
30 /// Replay this KEL file and project its current key-state into a `did:webs`
31 /// DID document (the shape a did:webs/DID-core resolver reads).
32 #[clap(long, value_name = "KEL.json")]
33 pub from_kel: PathBuf,
34
35 /// The web domain the `did:webs` is anchored at — the host (optionally
36 /// `host%3Aport` and path segments) before the AID in `did:webs:<domain>:<aid>`.
37 #[clap(long, value_name = "DOMAIN")]
38 pub domain: String,
39}
40
41impl DidWebsCommand {
42 /// Run the command: replay the KEL and print the `did:webs` DID document.
43 pub fn execute(&self, _ctx: &CliConfig) -> Result<()> {
44 self.emit(&self.from_kel)
45 }
46
47 /// Replay a KEL file and print the projected `did:webs` DID document.
48 fn emit(&self, kel_path: &Path) -> Result<()> {
49 let path = expand_tilde(kel_path)?;
50 let json = std::fs::read_to_string(&path)
51 .map_err(|e| anyhow!("read KEL {}: {e}", path.display()))?;
52 let events = parse_kel_json(&json).map_err(|e| anyhow!("parse KEL: {e}"))?;
53 // A KEL file the operator hands us is a local, self-owned artifact — the
54 // reviewable trust assertion that structural replay requires.
55 let state = TrustedKel::from_trusted_source(&events)
56 .replay()
57 .map_err(|e| anyhow!("replay KEL: {e}"))?;
58 let doc = DidWebsDocument::from_key_state(&state, &self.domain)
59 .map_err(|e| anyhow!("project key-state into a did:webs document: {e}"))?;
60 println!("{}", serde_json::to_string_pretty(&doc)?);
61 Ok(())
62 }
63}