Skip to main content

auths_sdk/
registration.rs

1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4
5use auths_core::ports::network::{NetworkError, RegistryClient};
6use auths_id::keri::Prefix;
7use auths_id::ports::registry::RegistryBackend;
8use auths_id::storage::attestation::AttestationSource;
9use auths_id::storage::identity::IdentityStorage;
10
11use crate::error::RegistrationError;
12use crate::result::RegistrationOutcome;
13
14/// Default registry URL used when no explicit registry endpoint is configured.
15pub const DEFAULT_REGISTRY_URL: &str = "https://auths-registry.fly.dev";
16
17#[derive(Serialize)]
18struct RegistryOnboardingPayload {
19    inception_event: serde_json::Value,
20    attestations: Vec<serde_json::Value>,
21    proof_url: Option<String>,
22}
23
24#[derive(Deserialize)]
25struct RegistrationResponse {
26    did_prefix: String,
27    platform_claims_indexed: usize,
28}
29
30/// Registers a local identity with a remote registry for public discovery.
31///
32/// Args:
33/// * `identity_storage`: Storage adapter for loading the local identity.
34/// * `registry`: Registry backend for reading KEL events.
35/// * `attestation_source`: Source for loading local attestations.
36/// * `registry_url`: Base URL of the target registry.
37/// * `proof_url`: Optional URL to a platform proof (e.g., GitHub gist).
38/// * `registry_client`: Network client for communicating with the registry.
39///
40/// Usage:
41/// ```ignore
42/// let outcome = register_identity(
43///     identity_storage, registry, attestation_source,
44///     "https://auths-registry.fly.dev", None, &http_client,
45/// ).await?;
46/// ```
47pub async fn register_identity(
48    identity_storage: Arc<dyn IdentityStorage + Send + Sync>,
49    registry: Arc<dyn RegistryBackend + Send + Sync>,
50    attestation_source: Arc<dyn AttestationSource + Send + Sync>,
51    registry_url: &str,
52    proof_url: Option<String>,
53    registry_client: &impl RegistryClient,
54) -> Result<RegistrationOutcome, RegistrationError> {
55    let identity = identity_storage
56        .load_identity()
57        .map_err(|e| RegistrationError::LocalDataError(e.to_string()))?;
58
59    let did_prefix = identity
60        .controller_did
61        .as_str()
62        .strip_prefix("did:keri:")
63        .ok_or_else(|| {
64            RegistrationError::LocalDataError(format!(
65                "Invalid DID format, expected 'did:keri:': {}",
66                identity.controller_did
67            ))
68        })?;
69
70    let prefix = Prefix::new_unchecked(did_prefix.to_string());
71    let inception = registry.get_event(&prefix, 0).map_err(|_| {
72        RegistrationError::LocalDataError(
73            "No KEL events found for identity. The identity may be corrupted.".to_string(),
74        )
75    })?;
76    let inception_event = serde_json::to_value(&inception)
77        .map_err(|e| RegistrationError::LocalDataError(e.to_string()))?;
78
79    let attestations = attestation_source
80        .load_all_attestations()
81        .unwrap_or_default();
82    let attestation_values: Vec<serde_json::Value> = attestations
83        .iter()
84        .filter_map(|a| serde_json::to_value(a).ok())
85        .collect();
86
87    let payload = RegistryOnboardingPayload {
88        inception_event,
89        attestations: attestation_values,
90        proof_url,
91    };
92
93    let json_body = serde_json::to_vec(&payload)
94        .map_err(|e| RegistrationError::LocalDataError(e.to_string()))?;
95
96    let registry_url = registry_url.trim_end_matches('/');
97    let response = registry_client
98        .post_json(registry_url, "v1/identities", &json_body)
99        .await
100        .map_err(RegistrationError::NetworkError)?;
101
102    match response.status {
103        201 => {
104            let body: RegistrationResponse =
105                serde_json::from_slice(&response.body).map_err(|e| {
106                    RegistrationError::NetworkError(NetworkError::InvalidResponse {
107                        detail: e.to_string(),
108                    })
109                })?;
110
111            Ok(RegistrationOutcome {
112                did_prefix: body.did_prefix,
113                registry: registry_url.to_string(),
114                platform_claims_indexed: body.platform_claims_indexed,
115            })
116        }
117        409 => Err(RegistrationError::AlreadyRegistered),
118        429 => Err(RegistrationError::QuotaExceeded),
119        _ => {
120            let body = String::from_utf8_lossy(&response.body);
121            Err(RegistrationError::NetworkError(
122                NetworkError::InvalidResponse {
123                    detail: format!("Registry error ({}): {}", response.status, body),
124                },
125            ))
126        }
127    }
128}