Skip to main content

tap_cli/commands/
did.rs

1use crate::error::{Error, Result};
2use crate::output::{print_success, OutputFormat};
3use clap::Subcommand;
4use serde::Serialize;
5use std::sync::Arc;
6use tap_agent::did::{
7    DIDGenerationOptions, DIDKeyGenerator, KeyType, MultiResolver, SyncDIDResolver,
8};
9use tap_agent::storage::KeyStorage;
10
11#[derive(Subcommand, Debug)]
12pub enum DidCommands {
13    /// Generate a new DID
14    Generate {
15        /// DID method (key or web)
16        #[arg(short, long, default_value = "key")]
17        method: String,
18        /// Key type (ed25519, p256, secp256k1)
19        #[arg(short = 't', long, default_value = "ed25519")]
20        key_type: String,
21        /// Domain for did:web
22        #[arg(short, long)]
23        domain: Option<String>,
24        /// Save to storage
25        #[arg(short, long)]
26        save: bool,
27        /// Set as default key
28        #[arg(long)]
29        default: bool,
30        /// Label for the key
31        #[arg(short, long)]
32        label: Option<String>,
33    },
34    /// Resolve a DID to its DID Document
35    Lookup {
36        /// DID to resolve
37        did: String,
38    },
39    /// Manage stored keys
40    Keys {
41        #[command(subcommand)]
42        cmd: Option<KeysCommands>,
43    },
44}
45
46#[derive(Subcommand, Debug)]
47pub enum KeysCommands {
48    /// List all stored keys
49    List,
50    /// View key details
51    View {
52        /// DID or label
53        did_or_label: String,
54    },
55    /// Set a key as default
56    SetDefault {
57        /// DID or label
58        did_or_label: String,
59    },
60    /// Delete a key
61    Delete {
62        /// DID or label
63        did_or_label: String,
64    },
65    /// Relabel a key
66    Relabel {
67        /// DID or label
68        did_or_label: String,
69        /// New label
70        new_label: String,
71    },
72}
73
74#[derive(Debug, Serialize)]
75struct GeneratedDidResponse {
76    did: String,
77    key_type: String,
78    public_key: String,
79    saved: bool,
80    is_default: bool,
81}
82
83#[derive(Debug, Serialize)]
84struct KeyInfo {
85    did: String,
86    label: String,
87    key_type: String,
88    public_key: String,
89    is_default: bool,
90}
91
92#[derive(Debug, Serialize)]
93struct KeyListResponse {
94    keys: Vec<KeyInfo>,
95    total: usize,
96}
97
98pub async fn handle(cmd: &DidCommands, format: OutputFormat) -> Result<()> {
99    match cmd {
100        DidCommands::Generate {
101            method,
102            key_type,
103            domain,
104            save,
105            default,
106            label,
107        } => {
108            handle_generate(
109                method,
110                key_type,
111                domain.as_deref(),
112                *save,
113                *default,
114                label.as_deref(),
115                format,
116            )
117            .await
118        }
119        DidCommands::Lookup { did } => handle_lookup(did, format).await,
120        DidCommands::Keys { cmd } => handle_keys(cmd.as_ref(), format).await,
121    }
122}
123
124async fn handle_generate(
125    method: &str,
126    key_type_str: &str,
127    domain: Option<&str>,
128    save: bool,
129    set_default: bool,
130    label: Option<&str>,
131    format: OutputFormat,
132) -> Result<()> {
133    let key_type = match key_type_str.to_lowercase().as_str() {
134        "ed25519" => KeyType::Ed25519,
135        _ => KeyType::Ed25519,
136    };
137
138    let did_options = DIDGenerationOptions { key_type };
139    let generator = DIDKeyGenerator::new();
140
141    let generated_key = match method.to_lowercase().as_str() {
142        "key" => generator.generate_did(did_options)?,
143        "web" => {
144            let domain =
145                domain.ok_or_else(|| Error::invalid_parameter("Domain is required for did:web"))?;
146            generator.generate_web_did(domain, did_options)?
147        }
148        _ => generator.generate_did(did_options)?,
149    };
150
151    if save {
152        let stored_key = if let Some(label) = label {
153            KeyStorage::from_generated_key_with_label(&generated_key, label)
154        } else {
155            KeyStorage::from_generated_key(&generated_key)
156        };
157
158        let mut storage = KeyStorage::load_default().unwrap_or_else(|_| KeyStorage::new());
159        storage.add_key(stored_key);
160
161        if set_default {
162            storage.default_did = Some(generated_key.did.clone());
163        }
164
165        storage
166            .save_default()
167            .map_err(|e| Error::command_failed(format!("Failed to save key: {}", e)))?;
168    }
169
170    let response = GeneratedDidResponse {
171        did: generated_key.did,
172        key_type: format!("{:?}", generated_key.key_type),
173        public_key: generated_key
174            .public_key
175            .iter()
176            .map(|b| format!("{:02x}", b))
177            .collect::<String>(),
178        saved: save,
179        is_default: set_default,
180    };
181    print_success(format, &response);
182    Ok(())
183}
184
185async fn handle_lookup(did: &str, format: OutputFormat) -> Result<()> {
186    let resolver = Arc::new(MultiResolver::default());
187    let did_doc = resolver.resolve(did).await?;
188
189    match did_doc {
190        Some(doc) => {
191            print_success(format, &doc);
192            Ok(())
193        }
194        None => Err(Error::command_failed(format!("DID not found: {}", did))),
195    }
196}
197
198async fn handle_keys(cmd: Option<&KeysCommands>, format: OutputFormat) -> Result<()> {
199    let mut storage = KeyStorage::load_default().unwrap_or_else(|_| KeyStorage::new());
200    let default_did = storage.default_did.clone();
201
202    match cmd {
203        Some(KeysCommands::List) | None => {
204            let keys: Vec<KeyInfo> = storage
205                .keys
206                .iter()
207                .map(|(did, key)| KeyInfo {
208                    did: did.clone(),
209                    label: key.label.clone(),
210                    key_type: format!("{:?}", key.key_type),
211                    public_key: key.public_key.clone(),
212                    is_default: default_did.as_deref() == Some(did.as_str()),
213                })
214                .collect();
215
216            let response = KeyListResponse {
217                total: keys.len(),
218                keys,
219            };
220            print_success(format, &response);
221            Ok(())
222        }
223        Some(KeysCommands::View { did_or_label }) => {
224            let key = storage
225                .find_by_label(did_or_label)
226                .or_else(|| storage.keys.get(did_or_label))
227                .ok_or_else(|| {
228                    Error::command_failed(format!("Key '{}' not found", did_or_label))
229                })?;
230
231            let info = KeyInfo {
232                did: key.did.clone(),
233                label: key.label.clone(),
234                key_type: format!("{:?}", key.key_type),
235                public_key: key.public_key.clone(),
236                is_default: default_did.as_deref() == Some(key.did.as_str()),
237            };
238            print_success(format, &info);
239            Ok(())
240        }
241        Some(KeysCommands::SetDefault { did_or_label }) => {
242            let did = if let Some(key) = storage.find_by_label(did_or_label) {
243                key.did.clone()
244            } else if storage.keys.contains_key(did_or_label) {
245                did_or_label.to_string()
246            } else {
247                return Err(Error::command_failed(format!(
248                    "Key '{}' not found",
249                    did_or_label
250                )));
251            };
252
253            storage.default_did = Some(did.clone());
254            storage
255                .save_default()
256                .map_err(|e| Error::command_failed(format!("Failed to save: {}", e)))?;
257
258            #[derive(Serialize)]
259            struct SetDefaultResponse {
260                did: String,
261                status: String,
262            }
263            print_success(
264                format,
265                &SetDefaultResponse {
266                    did,
267                    status: "default_set".to_string(),
268                },
269            );
270            Ok(())
271        }
272        Some(KeysCommands::Delete { did_or_label }) => {
273            let did = if let Some(key) = storage.find_by_label(did_or_label) {
274                key.did.clone()
275            } else if storage.keys.contains_key(did_or_label) {
276                did_or_label.to_string()
277            } else {
278                return Err(Error::command_failed(format!(
279                    "Key '{}' not found",
280                    did_or_label
281                )));
282            };
283
284            storage.keys.remove(&did);
285            if storage.default_did.as_deref() == Some(&did) {
286                storage.default_did = storage.keys.keys().next().cloned();
287            }
288
289            storage
290                .save_default()
291                .map_err(|e| Error::command_failed(format!("Failed to save: {}", e)))?;
292
293            #[derive(Serialize)]
294            struct DeleteResponse {
295                did: String,
296                status: String,
297            }
298            print_success(
299                format,
300                &DeleteResponse {
301                    did,
302                    status: "deleted".to_string(),
303                },
304            );
305            Ok(())
306        }
307        Some(KeysCommands::Relabel {
308            did_or_label,
309            new_label,
310        }) => {
311            let did = if let Some(key) = storage.find_by_label(did_or_label) {
312                key.did.clone()
313            } else if storage.keys.contains_key(did_or_label) {
314                did_or_label.to_string()
315            } else {
316                return Err(Error::command_failed(format!(
317                    "Key '{}' not found",
318                    did_or_label
319                )));
320            };
321
322            storage
323                .update_label(&did, new_label)
324                .map_err(|e| Error::command_failed(format!("Failed to relabel: {}", e)))?;
325            storage
326                .save_default()
327                .map_err(|e| Error::command_failed(format!("Failed to save: {}", e)))?;
328
329            #[derive(Serialize)]
330            struct RelabelResponse {
331                did: String,
332                new_label: String,
333                status: String,
334            }
335            print_success(
336                format,
337                &RelabelResponse {
338                    did,
339                    new_label: new_label.clone(),
340                    status: "relabeled".to_string(),
341                },
342            );
343            Ok(())
344        }
345    }
346}