Skip to main content

aep_platform/
types.rs

1use std::{collections::BTreeMap, sync::Arc, time::Duration};
2
3use aep_core::{
4    AssertionOperation, ClientAssertionClaims, ClientAssertionVerifyingKey, ErrorCode,
5    ProblemDetails, SigningAlgorithm,
6};
7use async_trait::async_trait;
8use futures::future::BoxFuture;
9use http::HeaderMap;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use time::OffsetDateTime;
13
14use crate::PlatformError;
15
16pub const DID_MEDIA_TYPE: &str = "application/did+json";
17pub const HOSTED_IDENTITY_DRAFT: &str = "draft-kavian-aep-platform-hosted-identity-01";
18pub const WELL_KNOWN_PATH: &str = "/.well-known/aep-platform";
19
20#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
21#[serde(rename_all = "lowercase")]
22pub enum ManagedAgentStatus {
23    Active,
24    Revoked,
25    Suspended,
26    Terminated,
27}
28
29impl ManagedAgentStatus {
30    pub const fn as_str(self) -> &'static str {
31        match self {
32            Self::Active => "active",
33            Self::Revoked => "revoked",
34            Self::Suspended => "suspended",
35            Self::Terminated => "terminated",
36        }
37    }
38}
39
40#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
41pub struct DiscoveryDocument {
42    pub aep_version: String,
43    pub endpoints: DiscoveryEndpoints,
44    pub http: DiscoveryHttp,
45    pub identity: DiscoveryIdentity,
46    pub platform: DiscoveryPlatform,
47    pub signing: DiscoverySigning,
48    #[serde(flatten)]
49    pub additional: BTreeMap<String, Value>,
50}
51
52#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
53pub struct DiscoveryEndpoints {
54    #[serde(
55        default,
56        deserialize_with = "deserialize_optional_non_null",
57        skip_serializing_if = "Option::is_none"
58    )]
59    pub hosted_verification: Option<String>,
60    pub lifecycle: String,
61    pub list: String,
62    pub provision: String,
63    pub sign: String,
64    #[serde(flatten)]
65    pub additional: BTreeMap<String, Value>,
66}
67
68#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
69pub struct DiscoveryHttp {
70    pub endpoint_base: String,
71    #[serde(flatten)]
72    pub additional: BTreeMap<String, Value>,
73}
74
75#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
76pub struct DiscoveryIdentity {
77    pub did_methods: Vec<String>,
78    pub did_url_template: String,
79    #[serde(flatten)]
80    pub additional: BTreeMap<String, Value>,
81}
82
83#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
84pub struct DiscoveryPlatform {
85    #[serde(
86        default,
87        deserialize_with = "deserialize_optional_non_null",
88        skip_serializing_if = "Option::is_none"
89    )]
90    pub did: Option<String>,
91    pub hosted_verification: bool,
92    pub name: String,
93    #[serde(flatten)]
94    pub additional: BTreeMap<String, Value>,
95}
96
97#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
98pub struct DiscoverySigning {
99    pub algorithms: Vec<SigningAlgorithm>,
100    pub default_lifetime_seconds: String,
101    #[serde(flatten)]
102    pub additional: BTreeMap<String, Value>,
103}
104
105#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
106pub struct AgentIdentity {
107    pub agent_did: String,
108    pub agent_identity_id: String,
109    pub created_at: String,
110    pub did_document_url: String,
111    pub key_id: String,
112    pub service_did: String,
113    pub signing_algorithms: Vec<SigningAlgorithm>,
114    pub status: ManagedAgentStatus,
115    pub updated_at: String,
116    #[serde(flatten)]
117    pub additional: BTreeMap<String, Value>,
118}
119
120#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
121pub struct AgentIdentityListResponse {
122    pub count: String,
123    pub data: Vec<AgentIdentity>,
124    pub total: String,
125    #[serde(flatten)]
126    pub additional: BTreeMap<String, Value>,
127}
128
129#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
130#[serde(deny_unknown_fields)]
131pub struct ProvisionRequest {
132    pub service_did: String,
133}
134
135#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
136#[serde(deny_unknown_fields)]
137pub struct LifecycleRequest {
138    pub status: ManagedAgentStatus,
139}
140
141#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
142#[serde(rename_all = "lowercase")]
143pub enum SignStatus {
144    Completed,
145    Pending,
146}
147
148#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
149#[serde(deny_unknown_fields)]
150pub struct SignRequest {
151    pub jti: String,
152    #[serde(
153        default,
154        deserialize_with = "deserialize_optional_non_null",
155        skip_serializing_if = "Option::is_none"
156    )]
157    pub lifetime_seconds: Option<String>,
158    pub op: AssertionOperation,
159    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
160    pub platform_context: BTreeMap<String, Value>,
161    #[serde(
162        default,
163        deserialize_with = "deserialize_optional_non_null",
164        skip_serializing_if = "Option::is_none"
165    )]
166    pub resource: Option<String>,
167    pub service_did: String,
168}
169
170#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
171pub struct SignResponse {
172    #[serde(
173        default,
174        deserialize_with = "deserialize_optional_non_null",
175        skip_serializing_if = "Option::is_none"
176    )]
177    pub agent_did: Option<String>,
178    #[serde(
179        default,
180        deserialize_with = "deserialize_optional_non_null",
181        skip_serializing_if = "Option::is_none"
182    )]
183    pub client_assertion: Option<String>,
184    #[serde(
185        default,
186        deserialize_with = "deserialize_optional_non_null",
187        skip_serializing_if = "Option::is_none"
188    )]
189    pub expires_at: Option<String>,
190    #[serde(
191        default,
192        deserialize_with = "deserialize_optional_non_null",
193        skip_serializing_if = "Option::is_none"
194    )]
195    pub issued_at: Option<String>,
196    #[serde(
197        default,
198        deserialize_with = "deserialize_optional_non_null",
199        skip_serializing_if = "Option::is_none"
200    )]
201    pub jti: Option<String>,
202    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
203    pub platform_context: BTreeMap<String, Value>,
204    #[serde(
205        default,
206        deserialize_with = "deserialize_optional_non_null",
207        skip_serializing_if = "Option::is_none"
208    )]
209    pub retry_after_seconds: Option<String>,
210    #[serde(
211        default,
212        deserialize_with = "deserialize_optional_non_null",
213        skip_serializing_if = "Option::is_none"
214    )]
215    pub service_did: Option<String>,
216    pub status: SignStatus,
217    #[serde(flatten)]
218    pub additional: BTreeMap<String, Value>,
219}
220
221#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
222#[serde(deny_unknown_fields)]
223pub struct VerificationRequest {
224    pub client_assertion: String,
225    pub op: AssertionOperation,
226    #[serde(
227        default,
228        deserialize_with = "deserialize_optional_non_null",
229        skip_serializing_if = "Option::is_none"
230    )]
231    pub resource: Option<String>,
232    pub service_did: String,
233}
234
235#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
236pub struct VerificationResponse {
237    #[serde(
238        default,
239        deserialize_with = "deserialize_optional_non_null",
240        skip_serializing_if = "Option::is_none"
241    )]
242    pub agent_did: Option<String>,
243    #[serde(
244        default,
245        deserialize_with = "deserialize_optional_non_null",
246        skip_serializing_if = "Option::is_none"
247    )]
248    pub agent_identity_id: Option<String>,
249    #[serde(
250        default,
251        deserialize_with = "deserialize_optional_non_null",
252        skip_serializing_if = "Option::is_none"
253    )]
254    pub op: Option<AssertionOperation>,
255    pub reason: String,
256    pub service_did: String,
257    #[serde(
258        default,
259        deserialize_with = "deserialize_optional_non_null",
260        skip_serializing_if = "Option::is_none"
261    )]
262    pub status: Option<ManagedAgentStatus>,
263    pub verified: bool,
264    #[serde(flatten)]
265    pub additional: BTreeMap<String, Value>,
266}
267
268#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
269#[serde(deny_unknown_fields)]
270pub struct DidVerificationMethod {
271    pub controller: String,
272    pub id: String,
273    #[serde(rename = "publicKeyJwk")]
274    pub public_key_jwk: Value,
275    #[serde(rename = "type")]
276    pub method_type: String,
277}
278
279#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
280#[serde(deny_unknown_fields)]
281pub struct DidDocument {
282    #[serde(rename = "@context")]
283    pub context: Vec<String>,
284    #[serde(rename = "assertionMethod")]
285    pub assertion_method: Vec<String>,
286    pub authentication: Vec<String>,
287    #[serde(rename = "capabilityInvocation")]
288    pub capability_invocation: Vec<String>,
289    pub id: String,
290    #[serde(rename = "verificationMethod")]
291    pub verification_method: Vec<DidVerificationMethod>,
292}
293
294#[derive(Clone, Debug, PartialEq)]
295pub enum ResponseBody<T> {
296    Success(T),
297    Problem(Box<ProblemDetails>),
298}
299
300#[derive(Clone, Debug, PartialEq)]
301pub struct PlatformResponse<T> {
302    pub body: ResponseBody<T>,
303    pub headers: HeaderMap,
304    pub status: u16,
305}
306
307impl<T: Serialize> PlatformResponse<T> {
308    pub fn to_json(&self) -> Result<Value, serde_json::Error> {
309        match &self.body {
310            ResponseBody::Success(value) => serde_json::to_value(value),
311            ResponseBody::Problem(value) => serde_json::to_value(value),
312        }
313    }
314}
315
316#[derive(Clone, Debug, Default, Eq, PartialEq)]
317pub struct RequestContext {
318    pub authorization: Option<String>,
319    pub idempotency_key: Option<String>,
320    pub now: Option<OffsetDateTime>,
321    pub principal: String,
322    pub request_id: Option<String>,
323}
324
325#[derive(Clone, Debug, PartialEq)]
326pub struct IdentityRecord {
327    pub agent_did: String,
328    pub agent_did_id: String,
329    pub agent_identity_id: String,
330    pub created_at: OffsetDateTime,
331    pub did_document_url: String,
332    pub key_id: String,
333    pub principal: String,
334    pub service_did: String,
335    pub signing_algorithms: Vec<SigningAlgorithm>,
336    pub status: ManagedAgentStatus,
337    pub updated_at: OffsetDateTime,
338}
339
340#[derive(Clone, Debug, Default, Eq, PartialEq)]
341pub struct IdentityListQuery {
342    pub descending: bool,
343    pub limit: usize,
344    pub offset: usize,
345    pub service_did: Option<String>,
346    pub status: Option<ManagedAgentStatus>,
347}
348
349#[derive(Clone, Debug, PartialEq)]
350pub struct IdentityListResult {
351    pub identities: Vec<IdentityRecord>,
352    pub total: usize,
353}
354
355pub type IdentityCreation =
356    Box<dyn FnOnce() -> BoxFuture<'static, Result<IdentityRecord, PlatformError>> + Send>;
357
358#[async_trait]
359pub trait IdentityStore: Send + Sync {
360    async fn find_or_create(
361        &self,
362        principal: &str,
363        service_did: &str,
364        create: IdentityCreation,
365    ) -> Result<IdentityRecord, PlatformError>;
366    async fn find_by_agent_did(
367        &self,
368        agent_did: &str,
369    ) -> Result<Option<IdentityRecord>, PlatformError>;
370    async fn find_by_agent_did_id(
371        &self,
372        agent_did_id: &str,
373    ) -> Result<Option<IdentityRecord>, PlatformError>;
374    async fn get(&self, agent_identity_id: &str) -> Result<Option<IdentityRecord>, PlatformError>;
375    async fn list(
376        &self,
377        principal: &str,
378        query: &IdentityListQuery,
379    ) -> Result<IdentityListResult, PlatformError>;
380    async fn update_status(
381        &self,
382        agent_identity_id: &str,
383        status: ManagedAgentStatus,
384        updated_at: OffsetDateTime,
385    ) -> Result<Option<IdentityRecord>, PlatformError>;
386}
387
388#[async_trait]
389pub trait KeyStore: Send + Sync {
390    async fn create_key(&self, identity: &IdentityRecord) -> Result<(), PlatformError>;
391    async fn did_verification_method(
392        &self,
393        identity: &IdentityRecord,
394    ) -> Result<DidVerificationMethod, PlatformError>;
395    async fn sign(
396        &self,
397        identity: &IdentityRecord,
398        claims: &ClientAssertionClaims,
399    ) -> Result<String, PlatformError>;
400    async fn verification_key(
401        &self,
402        identity: &IdentityRecord,
403    ) -> Result<ClientAssertionVerifyingKey, PlatformError>;
404}
405
406#[async_trait]
407pub trait ServiceDidResolver: Send + Sync {
408    async fn resolve(&self, service_did: &str) -> Result<bool, PlatformError>;
409}
410
411#[derive(Clone, Debug, PartialEq)]
412pub enum AuthorizationRequest {
413    GetIdentity {
414        identity: IdentityRecord,
415    },
416    ListIdentities {
417        query: IdentityListQuery,
418    },
419    Provision {
420        request: ProvisionRequest,
421    },
422    Sign {
423        identity: IdentityRecord,
424        request: SignRequest,
425    },
426    UpdateIdentity {
427        identity: IdentityRecord,
428        request: LifecycleRequest,
429    },
430    Verify {
431        identity: IdentityRecord,
432        request: VerificationRequest,
433    },
434}
435
436#[async_trait]
437pub trait Authorizer: Send + Sync {
438    async fn authorize(
439        &self,
440        request: &AuthorizationRequest,
441        context: &RequestContext,
442    ) -> Result<bool, PlatformError>;
443}
444
445#[async_trait]
446pub trait LifecyclePolicy: Send + Sync {
447    async fn can_sign(
448        &self,
449        identity: &IdentityRecord,
450        context: &RequestContext,
451    ) -> Result<bool, PlatformError>;
452    async fn can_transition(
453        &self,
454        identity: &IdentityRecord,
455        status: ManagedAgentStatus,
456        context: &RequestContext,
457    ) -> Result<bool, PlatformError>;
458    async fn can_verify(
459        &self,
460        identity: &IdentityRecord,
461        context: &RequestContext,
462    ) -> Result<bool, PlatformError>;
463}
464
465#[async_trait]
466pub trait ReplayStore: Send + Sync {
467    async fn consume(
468        &self,
469        key: &str,
470        expires_at: OffsetDateTime,
471        now: OffsetDateTime,
472    ) -> Result<bool, PlatformError>;
473}
474
475#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
476pub enum IdempotentOperation {
477    HostedVerification,
478    Provision,
479    Sign,
480}
481
482#[derive(Clone, Debug, Eq, PartialEq)]
483pub struct IdempotencyInput {
484    pub idempotency_key: String,
485    pub operation: IdempotentOperation,
486    pub principal: String,
487    pub request_hash: String,
488}
489
490#[derive(Clone, Debug, PartialEq)]
491pub struct StoredResponse {
492    pub body: Vec<u8>,
493    pub content_type: String,
494    pub created_at: OffsetDateTime,
495    pub headers: HeaderMap,
496    pub status: u16,
497}
498
499#[derive(Clone, Debug, PartialEq)]
500pub enum IdempotencyResult {
501    Created(StoredResponse),
502    Replayed(StoredResponse),
503    Conflict,
504}
505
506pub type IdempotencyOperation =
507    Box<dyn FnOnce() -> BoxFuture<'static, Result<StoredResponse, PlatformError>> + Send>;
508
509#[async_trait]
510pub trait IdempotencyStore: Send + Sync {
511    async fn execute(
512        &self,
513        input: IdempotencyInput,
514        operation: IdempotencyOperation,
515    ) -> Result<IdempotencyResult, PlatformError>;
516}
517
518#[async_trait]
519pub trait SignHandler: Send + Sync {
520    async fn sign(
521        &self,
522        identity: &IdentityRecord,
523        request: &SignRequest,
524        context: &RequestContext,
525    ) -> Result<Option<PlatformResponse<SignResponse>>, PlatformError>;
526}
527
528pub trait Clock: Send + Sync {
529    fn now(&self) -> OffsetDateTime;
530}
531
532#[derive(Clone, Debug, Default, Eq, PartialEq)]
533pub struct DiscoveryOptions {
534    pub endpoint_base: String,
535    pub hosted_verification_endpoint: Option<String>,
536    pub lifecycle_endpoint: String,
537    pub list_endpoint: String,
538    pub platform_did: Option<String>,
539    pub platform_name: String,
540    pub provision_endpoint: String,
541    pub sign_endpoint: String,
542}
543
544#[derive(Clone)]
545pub struct PlatformOptions {
546    pub agent_did_id_generator:
547        Option<Arc<dyn Fn() -> Result<String, PlatformError> + Send + Sync>>,
548    pub authorizer: Arc<dyn Authorizer>,
549    pub clock: Option<Arc<dyn Clock>>,
550    pub default_lifetime: Duration,
551    pub did_host: String,
552    pub did_path_prefix: String,
553    pub did_url_template: String,
554    pub discovery: DiscoveryOptions,
555    pub hosted_verification: bool,
556    pub identifier: Option<Arc<dyn Fn() -> Result<String, PlatformError> + Send + Sync>>,
557    pub idempotency_store: Option<Arc<dyn IdempotencyStore>>,
558    pub identity_store: Option<Arc<dyn IdentityStore>>,
559    pub key_store: Arc<dyn KeyStore>,
560    pub lifecycle_policy: Option<Arc<dyn LifecyclePolicy>>,
561    pub maximum_lifetime: Duration,
562    pub replay_store: Option<Arc<dyn ReplayStore>>,
563    pub service_did_resolver: Arc<dyn ServiceDidResolver>,
564    pub sign_handler: Option<Arc<dyn SignHandler>>,
565    pub signing_algorithms: Vec<SigningAlgorithm>,
566}
567
568impl PlatformOptions {
569    pub fn new(
570        authorizer: Arc<dyn Authorizer>,
571        key_store: Arc<dyn KeyStore>,
572        service_did_resolver: Arc<dyn ServiceDidResolver>,
573        did_host: impl Into<String>,
574        did_url_template: impl Into<String>,
575    ) -> Self {
576        Self {
577            agent_did_id_generator: None,
578            authorizer,
579            clock: None,
580            default_lifetime: aep_core::MAX_ASSERTION_LIFETIME,
581            did_host: did_host.into(),
582            did_path_prefix: "agents".to_owned(),
583            did_url_template: did_url_template.into(),
584            discovery: DiscoveryOptions::default(),
585            hosted_verification: false,
586            identifier: None,
587            idempotency_store: None,
588            identity_store: None,
589            key_store,
590            lifecycle_policy: None,
591            maximum_lifetime: aep_core::MAX_ASSERTION_LIFETIME,
592            replay_store: None,
593            service_did_resolver,
594            sign_handler: None,
595            signing_algorithms: vec![SigningAlgorithm::EdDsa, SigningAlgorithm::Es256],
596        }
597    }
598}
599
600pub(crate) fn lifecycle_error_code(status: ManagedAgentStatus) -> ErrorCode {
601    match status {
602        ManagedAgentStatus::Terminated => ErrorCode::IdentityTerminated,
603        ManagedAgentStatus::Revoked | ManagedAgentStatus::Suspended => ErrorCode::IdentitySuspended,
604        ManagedAgentStatus::Active => ErrorCode::IdentityUnavailable,
605    }
606}
607
608fn deserialize_optional_non_null<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
609where
610    D: serde::Deserializer<'de>,
611    T: Deserialize<'de>,
612{
613    T::deserialize(deserializer).map(Some)
614}