1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub enum IdentitySelector {
7 Default,
8 Id(crate::ids::IdentityId),
9 Did(crate::ids::Did),
10 Handle(crate::ids::Handle),
11 LocalAlias(String),
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct IdentitySummary {
16 pub id: crate::ids::IdentityId,
17 pub did: crate::ids::Did,
18 pub handle: Option<crate::ids::Handle>,
19 pub display_name: Option<String>,
20 pub local_alias: Option<String>,
21 pub device_id: Option<String>,
22 pub is_default: bool,
23 pub readiness: IdentityReadiness,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct IdentityReadiness {
28 pub ready_for_auth: bool,
29 pub ready_for_messaging: bool,
30 pub missing: Vec<IdentityMissingItem>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum IdentitySecretStorageBackend {
36 FileCompat,
37 Vault,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct IdentityVaultStatus {
42 pub identity: IdentitySummary,
43 pub storage_policy: crate::core::IdentitySecretStoragePolicy,
44 pub selected_backend: IdentitySecretStorageBackend,
45 pub vault_available: bool,
46 pub vault_metadata_present: bool,
47 pub vault_metadata_verified: bool,
48 pub workspace_id: Option<String>,
49 pub device_id: Option<String>,
50 pub plaintext_compat_retained: Option<bool>,
51 pub missing: Vec<String>,
52 pub warnings: Vec<String>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct IdentityVaultMigrationReport {
57 pub identity: IdentitySummary,
58 pub status: IdentityVaultStatus,
59 pub migrated: bool,
60 pub verified: bool,
61 pub plaintext_compat_retained: bool,
62 pub warnings: Vec<String>,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct IdentityVaultVerificationReport {
67 pub identity: IdentitySummary,
68 pub status: IdentityVaultStatus,
69 pub verified: bool,
70 pub warnings: Vec<String>,
71}
72
73#[derive(Clone, PartialEq, Eq)]
74pub struct HostedIdentityMaterial {
75 pub identity_id: String,
76 pub did: String,
77 pub handle: Option<String>,
78 pub display_name: Option<String>,
79 pub did_document: serde_json::Value,
80 pub default_signing_private_key_pem: String,
81 pub e2ee_agreement_private_key_pem: String,
82 pub auth_token: Option<String>,
83}
84
85impl std::fmt::Debug for HostedIdentityMaterial {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("HostedIdentityMaterial")
88 .field("identity_id", &self.identity_id)
89 .field("did", &self.did)
90 .field("handle", &self.handle)
91 .field("display_name", &self.display_name)
92 .field("did_document", &"<redacted-hosted-did-document>")
93 .field("default_signing_private_key_pem", &"<redacted-private-key>")
94 .field("e2ee_agreement_private_key_pem", &"<redacted-private-key>")
95 .field(
96 "auth_token",
97 &self.auth_token.as_ref().map(|_| "<redacted-token>"),
98 )
99 .finish()
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub enum IdentityMissingItem {
105 DidDocument,
106 PrivateKey,
107 AuthState,
108 Handle,
109 MessageEndpoint,
110 Other(String),
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct RegisterHandleRequest {
115 pub local_alias: Option<String>,
116 pub requested_handle: crate::ids::Handle,
117 pub verification: VerificationInput,
118 pub invite_code: Option<String>,
119 pub profile: InitialProfile,
120 pub make_default: bool,
121}
122
123pub const DAEMON_SUBKEY_PACKAGE_SCHEMA_V1: &str = "awiki.daemon.user_subkey_package.v1";
124pub const DAEMON_SUBKEY_PACKAGE_SCHEMA_V2: &str = "awiki.daemon.user_subkey_package.v2";
125pub const DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM: &str = "pem";
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct DaemonSubkeyPrivatePackage {
129 pub schema: String,
130 pub user_did: crate::ids::Did,
131 pub verification_method: String,
132 pub key_type: String,
133 pub key_algorithm: Option<String>,
134 pub public_key_multibase: String,
135 pub private_key_encoding: String,
136 pub private_key_pem: String,
137 pub private_key_multibase: String,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143pub struct DaemonSubkeyAuthorizationRevokeResult {
144 pub user_did: crate::ids::Did,
145 pub verification_method: String,
146 pub updated: bool,
147}
148
149impl DaemonSubkeyPrivatePackage {
150 pub fn new_v2_pem(
151 user_did: crate::ids::Did,
152 verification_method: String,
153 key_type: String,
154 key_algorithm: Option<String>,
155 public_key_multibase: String,
156 private_key_pem: String,
157 ) -> Self {
158 Self {
159 schema: DAEMON_SUBKEY_PACKAGE_SCHEMA_V2.to_owned(),
160 user_did,
161 verification_method,
162 key_type,
163 key_algorithm,
164 public_key_multibase,
165 private_key_encoding: DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM.to_owned(),
166 private_key_multibase: private_key_pem.clone(),
167 private_key_pem,
168 }
169 }
170
171 pub fn private_key_material(&self) -> &str {
172 if !self.private_key_pem.trim().is_empty() {
173 &self.private_key_pem
174 } else {
175 &self.private_key_multibase
176 }
177 }
178
179 pub fn is_v2_pem(&self) -> bool {
180 self.schema == DAEMON_SUBKEY_PACKAGE_SCHEMA_V2
181 && self.private_key_encoding == DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM
182 && !self.private_key_pem.trim().is_empty()
183 }
184}
185
186impl Serialize for DaemonSubkeyPrivatePackage {
187 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
188 where
189 S: Serializer,
190 {
191 #[derive(Serialize)]
192 struct Wire<'a> {
193 schema: &'a str,
194 user_did: &'a crate::ids::Did,
195 verification_method: &'a str,
196 key_type: &'a str,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 key_algorithm: Option<&'a str>,
199 public_key_multibase: &'a str,
200 private_key_encoding: &'a str,
201 private_key_pem: &'a str,
202 }
203
204 let private_key_pem = self.private_key_material();
205 let private_key_encoding = if self.private_key_encoding.trim().is_empty() {
206 DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM
207 } else {
208 self.private_key_encoding.trim()
209 };
210 Wire {
211 schema: DAEMON_SUBKEY_PACKAGE_SCHEMA_V2,
212 user_did: &self.user_did,
213 verification_method: &self.verification_method,
214 key_type: &self.key_type,
215 key_algorithm: self.key_algorithm.as_deref(),
216 public_key_multibase: &self.public_key_multibase,
217 private_key_encoding,
218 private_key_pem,
219 }
220 .serialize(serializer)
221 }
222}
223
224impl<'de> Deserialize<'de> for DaemonSubkeyPrivatePackage {
225 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
226 where
227 D: Deserializer<'de>,
228 {
229 #[derive(Deserialize)]
230 struct Wire {
231 schema: String,
232 user_did: crate::ids::Did,
233 verification_method: String,
234 key_type: String,
235 #[serde(default)]
236 key_algorithm: Option<String>,
237 public_key_multibase: String,
238 #[serde(default)]
239 private_key_encoding: Option<String>,
240 #[serde(default)]
241 private_key_pem: Option<String>,
242 #[serde(default)]
243 private_key_multibase: Option<String>,
244 }
245
246 let wire = Wire::deserialize(deserializer)?;
247 let private_key_pem = wire
248 .private_key_pem
249 .as_deref()
250 .map(str::trim)
251 .filter(|value| !value.is_empty())
252 .map(ToOwned::to_owned)
253 .or_else(|| {
254 wire.private_key_multibase
255 .as_deref()
256 .map(str::trim)
257 .filter(|value| !value.is_empty())
258 .map(ToOwned::to_owned)
259 })
260 .ok_or_else(|| serde::de::Error::missing_field("private_key_pem"))?;
261 let private_key_encoding = wire
262 .private_key_encoding
263 .as_deref()
264 .map(str::trim)
265 .filter(|value| !value.is_empty())
266 .unwrap_or(DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM)
267 .to_string();
268 Ok(Self {
269 schema: wire.schema,
270 user_did: wire.user_did,
271 verification_method: wire.verification_method,
272 key_type: wire.key_type,
273 key_algorithm: wire.key_algorithm,
274 public_key_multibase: wire.public_key_multibase,
275 private_key_encoding,
276 private_key_multibase: private_key_pem.clone(),
277 private_key_pem,
278 })
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283pub enum VerificationInput {
284 Otp {
285 code: String,
286 },
287 Phone {
288 phone: String,
289 otp: Option<String>,
290 },
291 Email {
292 email: String,
293 wait_for_verification: bool,
294 },
295 AlreadyVerified,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
299pub struct InitialProfile {
300 pub display_name: Option<String>,
301 pub avatar_url: Option<String>,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
305pub struct HandleRegistrationResult {
306 pub identity: Option<IdentitySummary>,
307 pub handle: crate::ids::Handle,
308 pub method: RegistrationMethod,
309 pub state: HandleRegistrationState,
310 pub default_identity_change: Option<DefaultIdentityChange>,
311 pub warnings: Vec<String>,
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
315pub enum RegistrationMethod {
316 Phone,
317 Email,
318 AlreadyVerified,
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
322pub enum HandleRegistrationState {
323 OtpSent,
324 EmailSent,
325 EmailPending,
326 Registered,
327}
328
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330pub struct DefaultIdentityChange {
331 pub previous: Option<IdentitySummary>,
332 pub next: IdentitySummary,
333 pub requires_default_identity_write: bool,
334 pub warnings: Vec<String>,
335}
336
337#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
338pub struct DeleteLocalIdentityResult {
339 pub deleted: IdentitySummary,
340 pub was_default: bool,
341 pub next_default: Option<IdentitySummary>,
342 pub warnings: Vec<String>,
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
346pub struct Profile {
347 pub subject: crate::ids::Did,
348 pub handle: Option<crate::ids::Handle>,
349 pub display_name: Option<String>,
350 pub bio: Option<String>,
351 pub description: Option<String>,
352 pub tags: Vec<String>,
353 pub markdown: Option<String>,
354 pub avatar_uri: Option<String>,
355 pub avatar_url: Option<String>,
356 pub profile_uri: Option<String>,
357 pub subject_type: Option<String>,
358 pub updated_at: Option<String>,
359 #[serde(
360 default,
361 rename = "versionId",
362 alias = "version_id",
363 skip_serializing_if = "Option::is_none"
364 )]
365 pub version_id: Option<String>,
366 pub ttl: Option<u64>,
367 pub proof: Option<serde_json::Value>,
368 pub metadata: Vec<ProfileAttribute>,
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
372pub struct ProfileAttribute {
373 pub key: String,
374 pub value: String,
375}
376
377impl Profile {
378 pub fn new(subject: crate::ids::Did) -> Self {
379 Self {
380 subject,
381 handle: None,
382 display_name: None,
383 bio: None,
384 description: None,
385 tags: Vec::new(),
386 markdown: None,
387 avatar_uri: None,
388 avatar_url: None,
389 profile_uri: None,
390 subject_type: None,
391 updated_at: None,
392 version_id: None,
393 ttl: None,
394 proof: None,
395 metadata: Vec::new(),
396 }
397 }
398
399 pub fn effective_description(&self) -> Option<&String> {
400 self.description.as_ref().or(self.bio.as_ref())
401 }
402
403 pub fn effective_avatar_uri(&self) -> Option<&String> {
404 self.avatar_uri.as_ref().or(self.avatar_url.as_ref())
405 }
406
407 pub fn to_wire_profile_value(&self) -> serde_json::Value {
408 let mut value = serde_json::Map::new();
409 value.insert(
410 "did".to_string(),
411 serde_json::Value::String(self.subject.as_str().to_string()),
412 );
413 value.insert(
414 "subject_did".to_string(),
415 serde_json::Value::String(self.subject.as_str().to_string()),
416 );
417 if let Some(handle) = self.handle.as_ref() {
418 value.insert(
419 "handle".to_string(),
420 serde_json::Value::String(handle.as_str().to_string()),
421 );
422 }
423 if let Some(display_name) = self.display_name.as_ref() {
424 value.insert(
425 "display_name".to_string(),
426 serde_json::Value::String(display_name.clone()),
427 );
428 value.insert(
429 "nick_name".to_string(),
430 serde_json::Value::String(display_name.clone()),
431 );
432 }
433 if let Some(description) = self.effective_description() {
434 value.insert(
435 "description".to_string(),
436 serde_json::Value::String(description.clone()),
437 );
438 }
439 if let Some(bio) = self.bio.as_ref().or(self.description.as_ref()) {
440 value.insert("bio".to_string(), serde_json::Value::String(bio.clone()));
441 }
442 if !self.tags.is_empty() {
443 value.insert("tags".to_string(), serde_json::json!(self.tags));
444 }
445 if let Some(markdown) = self.markdown.as_ref() {
446 value.insert(
447 "profile_md".to_string(),
448 serde_json::Value::String(markdown.clone()),
449 );
450 }
451 if let Some(avatar_uri) = self.effective_avatar_uri() {
452 value.insert(
453 "avatar_uri".to_string(),
454 serde_json::Value::String(avatar_uri.clone()),
455 );
456 }
457 if let Some(avatar_url) = self.avatar_url.as_ref().or(self.avatar_uri.as_ref()) {
458 value.insert(
459 "avatar_url".to_string(),
460 serde_json::Value::String(avatar_url.clone()),
461 );
462 }
463 if let Some(profile_uri) = self.profile_uri.as_ref() {
464 value.insert(
465 "profile_uri".to_string(),
466 serde_json::Value::String(profile_uri.clone()),
467 );
468 }
469 if let Some(subject_type) = self.subject_type.as_ref() {
470 value.insert(
471 "subject_type".to_string(),
472 serde_json::Value::String(subject_type.clone()),
473 );
474 }
475 if let Some(updated_at) = self.updated_at.as_ref() {
476 value.insert(
477 "updated_at".to_string(),
478 serde_json::Value::String(updated_at.clone()),
479 );
480 value.insert(
481 "updated".to_string(),
482 serde_json::Value::String(updated_at.clone()),
483 );
484 }
485 if let Some(version_id) = self.version_id.as_ref() {
486 value.insert(
487 "versionId".to_string(),
488 serde_json::Value::String(version_id.clone()),
489 );
490 }
491 if let Some(ttl) = self.ttl {
492 value.insert("ttl".to_string(), serde_json::json!(ttl));
493 }
494 if let Some(proof) = self.proof.as_ref() {
495 value.insert("proof".to_string(), proof.clone());
496 }
497 if !self.metadata.is_empty() {
498 value.insert(
499 "metadata".to_string(),
500 serde_json::Value::Object(
501 self.metadata
502 .iter()
503 .map(|attribute| {
504 (
505 attribute.key.clone(),
506 serde_json::Value::String(attribute.value.clone()),
507 )
508 })
509 .collect(),
510 ),
511 );
512 }
513 serde_json::Value::Object(value)
514 }
515}
516
517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
518pub struct ProfilePatch {
519 pub display_name: Option<String>,
520 pub bio: Option<String>,
521 pub tags: Option<Vec<String>>,
522 pub markdown: Option<String>,
523 pub avatar_uri: Option<String>,
524 pub avatar_url: Option<String>,
525}
526
527#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
528pub struct ContactBindingRequest {
529 pub method: ContactBindingMethod,
530 pub wait_for_email_verification: bool,
531}
532
533#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
534pub enum ContactBindingMethod {
535 Phone { phone: String, otp: Option<String> },
536 Email { email: String },
537}
538
539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
540pub struct ContactBindingResult {
541 pub method: ContactBindingMethodKind,
542 pub target: String,
543 pub state: ContactBindingState,
544 #[serde(skip)]
545 raw_response: Option<serde_json::Value>,
546 pub warnings: Vec<String>,
547}
548
549impl ContactBindingResult {
550 pub(crate) fn with_raw_response(
551 method: ContactBindingMethodKind,
552 target: String,
553 state: ContactBindingState,
554 raw_response: Option<serde_json::Value>,
555 warnings: Vec<String>,
556 ) -> Self {
557 Self {
558 method,
559 target,
560 state,
561 raw_response,
562 warnings,
563 }
564 }
565
566 pub fn response_json(&self) -> Option<&serde_json::Value> {
567 self.raw_response.as_ref()
568 }
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
572pub enum ContactBindingMethodKind {
573 Phone,
574 Email,
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
578pub enum ContactBindingState {
579 OtpSent,
580 EmailSent,
581 Pending,
582 Completed,
583}
584
585#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
586pub struct RecoverHandleRequest {
587 pub handle: crate::ids::Handle,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub raw_handle: Option<String>,
590 pub phone: String,
591 pub otp: Option<String>,
592 pub generated_identity: Option<RecoverGeneratedIdentity>,
593 #[serde(default, skip_serializing_if = "Option::is_none")]
594 pub local_finalize: Option<RecoverHandleLocalFinalizeRequest>,
595}
596
597#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
598pub struct RecoverGeneratedIdentity {
599 pub did: crate::ids::Did,
600 pub unique_id: String,
601 pub did_document: serde_json::Value,
602}
603
604#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
605pub struct RecoverHandleLocalFinalizeRequest {
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub raw_handle: Option<String>,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub active_identity_name: Option<String>,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub config_file_path: Option<PathBuf>,
612}
613
614#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
615pub struct RecoverHandlePlanRequest {
616 pub handle: crate::ids::Handle,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
618 pub raw_handle: Option<String>,
619 pub phone: String,
620 pub otp: Option<String>,
621}
622
623#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
624pub struct RecoverHandlePlan {
625 pub action: String,
626 pub target_handle: String,
627 pub identity_name: String,
628 pub final_identity_name: String,
629 pub temp_identity_name: String,
630 pub same_handle_candidates: Vec<RecoverLocalIdentitySummary>,
631 pub excluded_identities: Vec<RecoverLocalIdentitySummary>,
632 pub backup_path: String,
633 pub phone: String,
634 pub remote_calls: Vec<String>,
635 pub local_writes: Option<Vec<String>>,
636}
637
638#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
639pub struct RecoverHandleResult {
640 pub handle: crate::ids::Handle,
641 pub phone: String,
642 pub state: RecoverHandleState,
643 pub recovered_identity: Option<RecoveredIdentity>,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub local_recovery: Option<RecoverHandleLocalResult>,
646 #[serde(skip)]
647 raw_response: Option<serde_json::Value>,
648 pub warnings: Vec<String>,
649}
650
651impl RecoverHandleResult {
652 pub(crate) fn with_raw_response(
653 handle: crate::ids::Handle,
654 phone: String,
655 state: RecoverHandleState,
656 recovered_identity: Option<RecoveredIdentity>,
657 local_recovery: Option<RecoverHandleLocalResult>,
658 raw_response: Option<serde_json::Value>,
659 warnings: Vec<String>,
660 ) -> Self {
661 Self {
662 handle,
663 phone,
664 state,
665 recovered_identity,
666 local_recovery,
667 raw_response,
668 warnings,
669 }
670 }
671
672 pub fn response_json(&self) -> Option<&serde_json::Value> {
673 self.raw_response.as_ref()
674 }
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
678pub enum RecoverHandleState {
679 OtpSent,
680 Recovered,
681}
682
683#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
684pub struct RecoveredIdentity {
685 pub identity: IdentitySummary,
686 pub user_id: Option<String>,
687 pub access_token_present: bool,
688}
689
690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
691pub struct RecoverHandleLocalResult {
692 pub identity: RecoverLocalIdentitySummary,
693 pub backup_path: String,
694 pub archived_identities: Vec<String>,
695 pub archived_dids: Vec<String>,
696 pub full_handle: String,
697 pub final_identity_name: String,
698 pub store_merge_counts: BTreeMap<String, i64>,
699 pub e2ee_cleanup_counts: BTreeMap<String, i64>,
700 pub default_updated: bool,
701 pub active_config_updated: bool,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
705pub struct RecoverLocalIdentitySummary {
706 pub identity_name: String,
707 pub did: String,
708 pub unique_id: String,
709 #[serde(skip_serializing_if = "String::is_empty")]
710 pub display_name: String,
711 #[serde(skip_serializing_if = "String::is_empty")]
712 pub handle: String,
713 #[serde(skip_serializing_if = "String::is_empty")]
714 pub full_handle: String,
715 #[serde(skip_serializing_if = "String::is_empty")]
716 pub created_at: String,
717 pub dir_name: String,
718 pub is_default: bool,
719 pub has_jwt: bool,
720 pub has_did_document: bool,
721 pub has_key1_private: bool,
722 pub has_key1_public: bool,
723 pub has_e2ee_signing_private: bool,
724 pub has_e2ee_agreement_private: bool,
725 pub user_state: RecoverLocalUserState,
726 #[serde(skip)]
727 pub user_id: String,
728}
729
730#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
731pub struct RecoverLocalUserState {
732 pub registration_state: String,
733 pub ready_for_messaging: bool,
734 #[serde(skip_serializing_if = "Vec::is_empty")]
735 pub missing: Vec<String>,
736}
737
738#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
739pub struct ReplaceDidPlanRequest {
740 pub identity: IdentitySummary,
741 pub linked_identity_names: Vec<String>,
742 pub planned_new_did: crate::ids::Did,
743 pub backup_path_preview: String,
744 pub old_dir_name: String,
745 pub is_public: Option<bool>,
746 pub is_agent: Option<bool>,
747 pub role: Option<String>,
748 pub endpoint_url: Option<String>,
749 pub affected_local_state: ReplaceDidAffectedLocalState,
750}
751
752#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
753pub struct ReplaceDidPlan {
754 pub action: String,
755 pub identity: IdentitySummary,
756 pub dangerous: bool,
757 pub risk_summary: Vec<String>,
758 pub backup_plan: ReplaceDidBackupPlan,
759 pub local_rebind_plan: ReplaceDidLocalRebindPlan,
760 pub affected_local_state: ReplaceDidAffectedLocalState,
761 pub remote_replace_did_call_preview: ReplaceDidRemoteCallPreview,
762 pub rollback_notes: Vec<String>,
763 pub local_writes: Vec<String>,
764 pub warnings: Vec<String>,
765}
766
767#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
768pub struct ReplaceDidBackupPlan {
769 pub required: bool,
770 pub backup_path_preview: String,
771 pub manifest_preview: ReplaceDidBackupManifestPreview,
772}
773
774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
775pub struct ReplaceDidBackupManifestPreview {
776 pub reason: String,
777 pub identity_name: String,
778 pub linked_identity_names: Vec<String>,
779 pub old_did: crate::ids::Did,
780 pub old_dir_name: String,
781 pub planned_new_did: crate::ids::Did,
782}
783
784#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
785pub struct ReplaceDidLocalRebindPlan {
786 pub required: bool,
787 pub old_owner_did: crate::ids::Did,
788 pub new_owner_did: crate::ids::Did,
789 pub destructive: bool,
790 pub dry_run_only: bool,
791}
792
793#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
794pub struct ReplaceDidAffectedLocalState {
795 pub store_rebind_counts: BTreeMap<String, i64>,
796 pub e2ee_cleanup_counts: BTreeMap<String, i64>,
797}
798
799#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
800pub struct ReplaceDidRemoteCallPreview {
801 pub endpoint: String,
802 pub method: String,
803 pub params: serde_json::Value,
804}
805
806#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
807pub struct ReplaceDidGeneratedIdentity {
808 pub did: crate::ids::Did,
809 pub unique_id: String,
810 pub did_document: serde_json::Value,
811}
812
813#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
814pub struct ReplaceDidExecutionRequest {
815 pub plan: ReplaceDidPlan,
816 pub generated_identity: ReplaceDidGeneratedIdentity,
817 pub is_public: Option<bool>,
818 pub is_agent: Option<bool>,
819 pub role: Option<String>,
820 pub endpoint_url: Option<String>,
821}
822
823#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
824pub struct ReplaceDidExecutionResult {
825 pub identity: IdentitySummary,
826 pub old_did: crate::ids::Did,
827 pub new_did: crate::ids::Did,
828 pub backup_path: String,
829 pub backup_manifest: ReplaceDidBackupManifestPreview,
830 pub affected_local_state: ReplaceDidAffectedLocalState,
831 pub remote_result: serde_json::Value,
832 pub warnings: Vec<String>,
833 pub recovery_notes: Vec<String>,
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839 use serde_json::json;
840
841 #[test]
842 fn binding_and_recover_results_keep_raw_response_internal_only() {
843 let binding = ContactBindingResult::with_raw_response(
844 ContactBindingMethodKind::Email,
845 "alice@example.test".to_string(),
846 ContactBindingState::EmailSent,
847 Some(json!({ "provider_state": "sent" })),
848 vec!["queued".to_string()],
849 );
850 let binding_json = serde_json::to_value(&binding).expect("serialize binding result");
851 assert_eq!(
852 binding
853 .response_json()
854 .and_then(|raw| raw.get("provider_state")),
855 Some(&json!("sent"))
856 );
857 assert!(binding_json.get("raw_response").is_none());
858 assert!(binding_json.get("raw_response").is_none());
859 assert!(binding_json.get("raw").is_none());
860
861 let recover = RecoverHandleResult::with_raw_response(
862 crate::ids::Handle::parse("alice", "example.test").expect("handle"),
863 "+15551234567".to_string(),
864 RecoverHandleState::OtpSent,
865 None,
866 None,
867 Some(json!({ "sent": true })),
868 Vec::new(),
869 );
870 let recover_json = serde_json::to_value(&recover).expect("serialize recover result");
871 assert_eq!(
872 recover.response_json().and_then(|raw| raw.get("sent")),
873 Some(&json!(true))
874 );
875 assert!(recover_json.get("raw_response").is_none());
876 assert!(recover_json.get("raw_response").is_none());
877 assert!(recover_json.get("raw").is_none());
878 }
879
880 #[test]
881 fn daemon_subkey_package_writes_v2_pem_without_legacy_private_field() {
882 let package = DaemonSubkeyPrivatePackage::new_v2_pem(
883 crate::ids::Did::parse("did:example:alice").unwrap(),
884 "did:example:alice#daemon-key-1".to_string(),
885 "Multikey/Ed25519".to_string(),
886 Some("Ed25519".to_string()),
887 "zPublic".to_string(),
888 "-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----".to_string(),
889 );
890
891 let value = serde_json::to_value(&package).unwrap();
892
893 assert_eq!(value["schema"], DAEMON_SUBKEY_PACKAGE_SCHEMA_V2);
894 assert_eq!(
895 value["private_key_encoding"],
896 DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM
897 );
898 assert_eq!(value["private_key_pem"], package.private_key_pem);
899 assert!(value.get("private_key_multibase").is_none());
900 }
901
902 #[test]
903 fn daemon_subkey_package_reads_legacy_v1_private_key_multibase() {
904 let package: DaemonSubkeyPrivatePackage = serde_json::from_value(json!({
905 "schema": DAEMON_SUBKEY_PACKAGE_SCHEMA_V1,
906 "user_did": "did:example:alice",
907 "verification_method": "did:example:alice#daemon-key-1",
908 "key_type": "Multikey/Ed25519",
909 "public_key_multibase": "zPublic",
910 "private_key_multibase": "-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----"
911 }))
912 .unwrap();
913
914 assert_eq!(package.schema, DAEMON_SUBKEY_PACKAGE_SCHEMA_V1);
915 assert_eq!(
916 package.private_key_encoding,
917 DAEMON_SUBKEY_PRIVATE_KEY_ENCODING_PEM
918 );
919 assert_eq!(package.private_key_pem, package.private_key_multibase);
920 assert!(package
921 .private_key_material()
922 .starts_with("-----BEGIN PRIVATE KEY-----"));
923 }
924}