use std::ops::ControlFlow;
use std::sync::Arc;
use auths_core::ports::clock::ClockProvider;
use auths_core::ports::id::UuidProvider;
use auths_core::signing::{PassphraseProvider, SecureSigner, StorageSigner};
use auths_core::storage::keychain::{KeyAlias, extract_public_key_bytes};
use auths_id::attestation::create::{AttestationInput, create_signed_attestation};
use auths_id::keri::{parse_did_keri, try_stage_anchor};
use auths_id::ports::registry::RegistryBackend;
use auths_id::storage::git_refs::AttestationMetadata;
use auths_id::storage::registry::backend::AtomicWriteBatch;
use auths_id::witness_config::WitnessParams;
use auths_verifier::core::Attestation;
pub use auths_verifier::core::Role;
use auths_verifier::types::CanonicalDid;
use chrono::{DateTime, Utc};
use crate::context::AuthsContext;
use crate::domains::org::error::OrgError;
use crate::identity::initialize_registry_identity;
pub struct OrgContext<'a> {
pub registry: &'a dyn RegistryBackend,
pub clock: &'a dyn ClockProvider,
pub uuid_provider: &'a dyn UuidProvider,
pub signer: &'a dyn SecureSigner,
pub passphrase_provider: &'a dyn PassphraseProvider,
pub witness_params: WitnessParams<'a>,
}
pub fn member_role_order(role: &Option<Role>) -> u8 {
match role {
Some(Role::Admin) => 0,
Some(Role::Member) => 1,
Some(Role::Readonly) => 2,
None => 3,
}
}
pub(crate) fn find_member(
backend: &dyn RegistryBackend,
org_prefix: &str,
member_did: &str,
) -> Result<Option<Attestation>, OrgError> {
let mut found: Option<Attestation> = None;
backend
.visit_org_member_attestations(org_prefix, &mut |entry| {
if entry.did.as_str() == member_did
&& let Ok(att) = &entry.attestation
{
found = Some(att.clone());
return ControlFlow::Break(());
}
ControlFlow::Continue(())
})
.map_err(OrgError::Storage)?;
Ok(found)
}
#[derive(Debug, Clone)]
pub enum OrgIdentifier {
Prefix(String),
Did(String),
}
impl OrgIdentifier {
pub fn parse(s: &str) -> Self {
if s.starts_with("did:") {
OrgIdentifier::Did(s.to_owned())
} else {
OrgIdentifier::Prefix(s.to_owned())
}
}
pub fn prefix(&self) -> &str {
match self {
OrgIdentifier::Prefix(p) => p,
OrgIdentifier::Did(d) => d.strip_prefix("did:keri:").unwrap_or(d),
}
}
}
impl From<&str> for OrgIdentifier {
fn from(s: &str) -> Self {
OrgIdentifier::parse(s)
}
}
#[derive(Debug, Clone)]
pub struct OrgCreated {
pub org_did: String,
pub org_prefix: String,
pub admin_did: String,
pub key_alias: String,
pub metadata: serde_json::Value,
}
fn build_org_metadata(
name: &str,
now: DateTime<Utc>,
extra: Option<serde_json::Value>,
) -> serde_json::Value {
let mut metadata = serde_json::json!({
"type": "org",
"name": name,
"created_at": now.to_rfc3339(),
});
if let Some(serde_json::Value::Object(add)) = extra
&& let Some(base) = metadata.as_object_mut()
{
for (k, v) in add {
if k != "type" && k != "name" {
base.insert(k, v);
}
}
}
metadata
}
pub fn create_org(
ctx: &AuthsContext,
name: &str,
admin_alias: &KeyAlias,
curve: auths_crypto::CurveType,
metadata: Option<serde_json::Value>,
) -> Result<OrgCreated, OrgError> {
let now = ctx.clock.now();
if ctx.identity_storage.load_identity().is_ok() {
return Err(OrgError::IdentityExists {
location: ctx
.repo_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "the registry".to_string()),
});
}
let metadata_json = build_org_metadata(name, now, metadata);
let (controller_did, alias) = initialize_registry_identity(
Arc::clone(&ctx.registry),
admin_alias,
ctx.passphrase_provider.as_ref(),
ctx.key_storage.as_ref(),
ctx.witness_config.as_ref(),
curve,
)
.map_err(OrgError::IdentityInit)?;
let managed = ctx
.identity_storage
.load_identity()
.map_err(|e| OrgError::Identity(e.to_string()))?;
let rid = managed.storage_id;
let (org_pk_bytes, org_curve) = extract_public_key_bytes(
ctx.key_storage.as_ref(),
admin_alias,
ctx.passphrase_provider.as_ref(),
)
.map_err(OrgError::CryptoError)?;
let signer = StorageSigner::new(Arc::clone(&ctx.key_storage));
#[allow(clippy::disallowed_methods)]
let org_did = CanonicalDid::new_unchecked(controller_did.to_string());
let meta = AttestationMetadata {
note: Some(format!("Organization '{name}' root admin")),
timestamp: Some(now),
expires_at: None,
};
let attestation = create_signed_attestation(
now,
AttestationInput {
rid: &rid,
identity_did: &controller_did,
subject: &org_did,
device_public_key: &org_pk_bytes,
device_curve: org_curve,
payload: Some(serde_json::json!({ "org_role": "admin", "org_name": name })),
meta: &meta,
identity_alias: Some(&alias),
device_alias: None,
delegated_by: None,
commit_sha: None,
signer_type: None,
},
&signer,
ctx.passphrase_provider.as_ref(),
)
.map_err(OrgError::Attestation)?;
let org_prefix =
parse_did_keri(controller_did.as_str()).map_err(|e| OrgError::InvalidDid(e.to_string()))?;
let mut batch = AtomicWriteBatch::new();
batch.stage_attestation(attestation);
try_stage_anchor(
ctx.registry.as_ref(),
&signer,
&alias,
ctx.passphrase_provider.as_ref(),
&org_prefix,
&serde_json::json!({}),
&mut batch,
)?;
ctx.registry
.commit_batch(&batch)
.map_err(OrgError::Storage)?;
Ok(OrgCreated {
org_did: controller_did.to_string(),
org_prefix: org_prefix.as_str().to_string(),
admin_did: org_did.to_string(),
key_alias: alias.as_str().to_string(),
metadata: metadata_json,
})
}
pub fn get_organization_member(
backend: &dyn RegistryBackend,
org_prefix: &str,
member_did: &str,
) -> Result<Attestation, OrgError> {
find_member(backend, org_prefix, member_did)?.ok_or_else(|| OrgError::MemberNotFound {
org: org_prefix.to_owned(),
did: member_did.to_owned(),
})
}