use std::collections::HashMap;
use std::sync::Arc;
use auths_core::signing::PassphraseProvider;
use auths_core::storage::keychain::{KeyAlias, KeyStorage};
use auths_id::{
identity::initialize::initialize_registry_identity,
ports::registry::RegistryBackend,
storage::identity::IdentityStorage,
witness_config::{WitnessConfig, WitnessPolicy, WitnessRef},
};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct NodeConfig {
pub identity: IdentityConfig,
pub witness: Option<WitnessOverride>,
}
#[derive(Debug, Deserialize)]
pub struct IdentityConfig {
#[serde(default = "default_key_alias")]
pub key_alias: String,
#[serde(default = "default_repo_path")]
pub repo_path: String,
#[serde(default = "default_preset")]
pub preset: String,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Deserialize)]
pub struct WitnessTomlEntry {
pub url: String,
pub aid: String,
}
#[derive(Debug, Deserialize)]
pub struct WitnessOverride {
#[serde(default)]
pub witnesses: Vec<WitnessTomlEntry>,
#[serde(default = "default_threshold")]
pub threshold: usize,
#[serde(default = "default_timeout_ms")]
pub timeout_ms: u64,
#[serde(default = "default_policy")]
pub policy: String,
}
fn default_key_alias() -> String {
"main".to_string()
}
fn default_repo_path() -> String {
auths_core::paths::auths_home()
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "~/.auths".to_string())
}
fn default_preset() -> String {
"default".to_string()
}
fn default_threshold() -> usize {
1
}
fn default_timeout_ms() -> u64 {
5000
}
fn default_policy() -> String {
"enforce".to_string()
}
#[derive(Debug)]
pub struct ProvisionResult {
pub controller_did: String,
pub key_alias: KeyAlias,
}
#[derive(Debug, thiserror::Error)]
pub enum ProvisionError {
#[error("failed to access platform keychain: {0}")]
KeychainUnavailable(String),
#[error("failed to initialize identity: {0}")]
IdentityInit(String),
#[error("identity already exists (use force=true to overwrite)")]
IdentityExists,
}
pub fn enforce_identity_state(
config: &NodeConfig,
force: bool,
passphrase_provider: &dyn PassphraseProvider,
keychain: &(dyn KeyStorage + Send + Sync),
registry: Arc<dyn RegistryBackend + Send + Sync>,
identity_storage: Arc<dyn IdentityStorage + Send + Sync>,
) -> Result<Option<ProvisionResult>, ProvisionError> {
if identity_storage.load_identity().is_ok() && !force {
return Ok(None);
}
let witness_config = build_witness_config(config.witness.as_ref());
let alias = KeyAlias::new_unchecked(&config.identity.key_alias);
let (controller_did, key_alias) = initialize_registry_identity(
registry,
&alias,
passphrase_provider,
keychain,
witness_config.as_ref(),
auths_crypto::CurveType::default(),
)
.map_err(|e| ProvisionError::IdentityInit(e.to_string()))?;
Ok(Some(ProvisionResult {
controller_did: controller_did.into_inner(),
key_alias,
}))
}
fn build_witness_config(witness: Option<&WitnessOverride>) -> Option<WitnessConfig> {
let w = witness?;
if w.witnesses.is_empty() {
return None;
}
let policy = match w.policy.as_str() {
"warn" => WitnessPolicy::Warn,
"skip" => WitnessPolicy::Skip,
_ => WitnessPolicy::Enforce,
};
let witnesses: Vec<WitnessRef> = w
.witnesses
.iter()
.filter_map(|e| {
let url = e.url.parse().ok()?;
let aid = auths_keri::Prefix::new(e.aid.clone()).ok()?;
Some(WitnessRef {
url,
aid,
operator_info: None,
})
})
.collect();
if witnesses.is_empty() {
return None;
}
Some(WitnessConfig {
witnesses,
threshold: w.threshold,
timeout_ms: w.timeout_ms,
policy,
..Default::default()
})
}