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}