use std::path::PathBuf;
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64;
use dialoguer::Input;
use crate::acl::{AclEntry, Role, store_acl_entry};
use crate::config::AppConfig;
use crate::contexts::{self, get_context};
use crate::keys;
use crate::keys::seed_store::create_seed_store;
use crate::keys::seeds::{get_active_seed_id, load_seed_bytes};
use crate::store::Store;
pub struct CreateDidKeyArgs {
pub config_path: Option<PathBuf>,
pub context: String,
pub admin: bool,
pub label: Option<String>,
}
pub async fn run_create_did_key(args: CreateDidKeyArgs) -> Result<(), Box<dyn std::error::Error>> {
let config = AppConfig::load(args.config_path)?;
let store = Store::open(&config.store)?;
let keys_ks = store.keyspace("keys")?;
let contexts_ks = store.keyspace("contexts")?;
let seed_store = create_seed_store(&config)?;
let active_seed_id = get_active_seed_id(&keys_ks).await?;
let seed = load_seed_bytes(&keys_ks, &*seed_store, Some(active_seed_id)).await?;
let ctx = match get_context(&contexts_ks, &args.context).await? {
Some(ctx) => ctx,
None => {
eprintln!("Context '{}' does not exist.", args.context);
let name: String = Input::new()
.with_prompt("Create it with name")
.default(args.context.clone())
.interact_text()?;
let ctx = contexts::create_context(&contexts_ks, &args.context, &name).await?;
eprintln!("Created context: {} ({})", ctx.id, ctx.base_path);
ctx
}
};
let label = args.label.as_deref().unwrap_or("did:key");
let (did, private_key_multibase) = keys::derive_and_store_did_key(
&seed,
&ctx.base_path,
&ctx.id,
label,
&keys_ks,
Some(active_seed_id),
)
.await?;
if args.admin {
let acl_ks = store.keyspace("acl")?;
let entry = AclEntry {
did: did.clone(),
role: Role::Admin,
label: args.label.clone(),
allowed_contexts: vec![args.context.clone()],
created_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
created_by: "cli:create-did-key".into(),
};
store_acl_entry(&acl_ks, &entry).await?;
eprintln!(
"ACL entry created: {} (admin, context: {})",
did, args.context
);
}
store.persist().await?;
eprintln!("DID: {did}");
if args.admin {
let vta_did = config.vta_did.unwrap_or_default();
let mut bundle = serde_json::json!({
"did": did,
"privateKeyMultibase": private_key_multibase,
"vtaDid": vta_did,
});
if let Some(url) = &config.public_url {
bundle["vtaUrl"] = serde_json::json!(url);
}
let bundle_json = serde_json::to_string(&bundle)?;
let credential = BASE64.encode(bundle_json.as_bytes());
eprintln!();
eprintln!("Credential:");
println!("{credential}");
}
Ok(())
}