Skip to main content

vta_cli_common/commands/
credentials.rs

1use vta_sdk::prelude::*;
2use vta_sdk::sealed_transfer::SealedPayloadV1;
3
4use super::acl::validate_role;
5use crate::local_keygen::generate_admin_did_key;
6use crate::sealed_producer::{SealedRecipient, emit_sealed_output, seal_for_recipient};
7
8pub async fn cmd_auth_credential_create(
9    client: &VtaClient,
10    role: String,
11    label: Option<String>,
12    contexts: Vec<String>,
13    recipient: SealedRecipient,
14) -> Result<(), Box<dyn std::error::Error>> {
15    validate_role(&role)?;
16
17    // Fetch VTA metadata for the credential bundle.
18    let config = client.get_config().await?;
19    let vta_did = config
20        .community_vta_did
21        .clone()
22        .ok_or("VTA DID not configured — cannot mint credential")?;
23    let vta_url = config.public_url.clone();
24
25    // Mint locally, then register the did:key via POST /acl. The private key
26    // never crosses the wire — it reaches the recipient only via the sealed
27    // bundle below.
28    let (bundle, did) = generate_admin_did_key(vta_did, vta_url);
29    let mut acl_req =
30        vta_sdk::client::CreateAclRequest::new(&did, &role).contexts(contexts.clone());
31    if let Some(l) = label {
32        acl_req = acl_req.label(l);
33    }
34    client.create_acl(acl_req).await?;
35
36    let sealed = seal_for_recipient(
37        &recipient,
38        &SealedPayloadV1::AdminCredential(Box::new(bundle)),
39    )
40    .await?;
41
42    println!("Credentials generated:");
43    println!("  DID:  {did}");
44    println!("  Role: {role}");
45    if !contexts.is_empty() {
46        println!("  Contexts: {}", contexts.join(", "));
47    }
48    if let Some(ref rlabel) = recipient.label {
49        println!("  Recipient: {rlabel}");
50    }
51    println!();
52    emit_sealed_output(&sealed, None)?;
53    Ok(())
54}