Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/auth/
settings.rs

1//! Authentication settings management for Eidetica
2//!
3//! This module provides a wrapper around Doc for managing authentication
4//! settings. Keys are indexed by pubkey to prevent collision bugs.
5//! Names are optional metadata that can be used as hints in signatures.
6
7use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11use super::errors::AuthError;
12use crate::{
13    Result,
14    auth::{
15        crypto::PublicKey,
16        types::{AuthKey, DelegatedTreeRef, KeyHint, KeyStatus, Permission, ResolvedAuth, SigKey},
17    },
18    crdt::{Doc, doc::Value},
19    entry::ID,
20};
21
22/// Authentication settings view/interface over Doc data
23///
24/// Keys are stored by pubkey in the "keys" sub-object.
25/// Delegations are stored by root tree ID in the "delegations" sub-object.
26/// Global permission is stored in the "global" sub-object.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct AuthSettings {
29    /// Doc data from _settings.auth - this is a view, not the authoritative copy
30    inner: Doc,
31}
32
33impl From<Doc> for AuthSettings {
34    fn from(doc: Doc) -> Self {
35        Self { inner: doc }
36    }
37}
38
39impl From<AuthSettings> for Doc {
40    fn from(settings: AuthSettings) -> Doc {
41        settings.inner
42    }
43}
44
45impl AuthSettings {
46    /// Create a new empty auth settings view
47    pub fn new() -> Self {
48        Self { inner: Doc::new() }
49    }
50
51    /// Get the underlying Doc for direct access
52    pub fn as_doc(&self) -> &Doc {
53        &self.inner
54    }
55
56    /// Get mutable access to the underlying Doc
57    pub fn as_doc_mut(&mut self) -> &mut Doc {
58        &mut self.inner
59    }
60
61    // ==================== Key Operations ====================
62
63    /// Add a new authentication key by pubkey (fails if key already exists)
64    ///
65    /// # Arguments
66    /// * `pubkey` - The public key
67    /// * `key` - The AuthKey containing permissions, status, and optional name
68    pub fn add_key(&mut self, pubkey: &PublicKey, key: AuthKey) -> Result<()> {
69        let pubkey_str = pubkey.to_string();
70
71        // Check if key already exists
72        if self.get_key_by_str(&pubkey_str).is_ok() {
73            return Err(AuthError::KeyAlreadyExists {
74                key_name: pubkey_str,
75            }
76            .into());
77        }
78
79        self.inner.set(format!("keys.{pubkey_str}"), key);
80        Ok(())
81    }
82
83    /// Explicitly overwrite an existing authentication key
84    pub fn overwrite_key(&mut self, pubkey: &PublicKey, key: AuthKey) -> Result<()> {
85        let pubkey_str = pubkey.to_string();
86        self.inner.set(format!("keys.{pubkey_str}"), key);
87        Ok(())
88    }
89
90    /// Get a key by its public key
91    pub fn get_key_by_pubkey(&self, pubkey: &PublicKey) -> Result<AuthKey> {
92        self.get_key_by_str(&pubkey.to_string())
93    }
94
95    /// Get a key by its public key string
96    ///
97    /// Internal helper used by `get_key_by_pubkey` and `resolve_hint` (which
98    /// already has strings from Doc storage).
99    fn get_key_by_str(&self, pubkey: &str) -> Result<AuthKey> {
100        match self.inner.get(format!("keys.{pubkey}")) {
101            Some(Value::Doc(doc)) => AuthKey::try_from(doc).map_err(|e| {
102                AuthError::InvalidKeyFormat {
103                    reason: e.to_string(),
104                }
105                .into()
106            }),
107            Some(_) => Err(AuthError::InvalidKeyFormat {
108                reason: format!("key '{pubkey}' is not a Doc"),
109            }
110            .into()),
111            None => Err(AuthError::KeyNotFound {
112                key_name: pubkey.to_string(),
113            }
114            .into()),
115        }
116    }
117
118    /// Find keys by name (may return multiple if names collide)
119    ///
120    /// Returns Vec of (pubkey, AuthKey) tuples sorted by pubkey for deterministic ordering.
121    pub fn find_keys_by_name(&self, name: &str) -> Vec<(String, AuthKey)> {
122        let mut matches = Vec::new();
123
124        // Get all keys and filter by name
125        if let Ok(all_keys) = self.get_all_keys() {
126            for (pubkey, auth_key) in all_keys {
127                if auth_key.name() == Some(name) {
128                    matches.push((pubkey, auth_key));
129                }
130            }
131        }
132
133        // Sort by pubkey for deterministic ordering
134        matches.sort_by(|a, b| a.0.cmp(&b.0));
135        matches
136    }
137
138    /// Get all authentication keys
139    pub fn get_all_keys(&self) -> Result<HashMap<String, AuthKey>> {
140        let mut result: HashMap<String, AuthKey> = HashMap::new();
141
142        // Get the "keys" sub-doc
143        if let Some(Value::Doc(keys_doc)) = self.inner.get("keys") {
144            for (pubkey, value) in keys_doc.iter() {
145                if let Value::Doc(key_doc) = value
146                    && let Ok(auth_key) = AuthKey::try_from(key_doc)
147                {
148                    result.insert(pubkey.clone(), auth_key);
149                }
150            }
151        }
152
153        Ok(result)
154    }
155
156    /// Rename a key by pubkey
157    ///
158    /// Updates only the display name of an existing key, preserving its
159    /// permissions and status.
160    pub fn rename_key(&mut self, pubkey: &PublicKey, name: Option<&str>) -> Result<()> {
161        let pubkey_str = pubkey.to_string();
162        let mut auth_key = self.get_key_by_str(&pubkey_str)?;
163        auth_key.set_name(name);
164        self.inner.set(format!("keys.{pubkey_str}"), auth_key);
165        Ok(())
166    }
167
168    /// Revoke a key by pubkey
169    pub fn revoke_key(&mut self, pubkey: &PublicKey) -> Result<()> {
170        let pubkey_str = pubkey.to_string();
171        let mut auth_key = self.get_key_by_str(&pubkey_str)?;
172        auth_key.set_status(KeyStatus::Revoked);
173        self.inner.set(format!("keys.{pubkey_str}"), auth_key);
174        Ok(())
175    }
176
177    // ==================== Delegation Operations ====================
178
179    /// Add or update a delegated tree reference
180    ///
181    /// The delegation is stored by root tree ID, extracted from `tree_ref.tree.root`.
182    /// This ensures collision-resistant storage similar to key storage by pubkey.
183    pub fn add_delegated_tree(&mut self, tree_ref: DelegatedTreeRef) -> Result<()> {
184        let root_id = tree_ref.tree.root.as_str().to_string();
185        self.inner.set(format!("delegations.{root_id}"), tree_ref);
186        Ok(())
187    }
188
189    /// Get a delegated tree reference by root tree ID
190    pub fn get_delegated_tree(&self, root_id: &ID) -> Result<DelegatedTreeRef> {
191        self.get_delegated_tree_by_str(root_id.as_str())
192    }
193
194    /// Get a delegated tree reference by root tree ID string
195    ///
196    /// This variant accepts a string directly, useful when the ID comes from
197    /// a `DelegationStep.tree` field which stores the root ID as a string.
198    pub fn get_delegated_tree_by_str(&self, root_id: &str) -> Result<DelegatedTreeRef> {
199        match self.inner.get(format!("delegations.{root_id}")) {
200            Some(Value::Doc(doc)) => DelegatedTreeRef::try_from(doc).map_err(|e| {
201                AuthError::InvalidAuthConfiguration {
202                    reason: format!("Invalid delegated tree format: {e}"),
203                }
204                .into()
205            }),
206            Some(_) => Err(AuthError::InvalidAuthConfiguration {
207                reason: format!("delegation '{root_id}' is not a Doc"),
208            }
209            .into()),
210            None => Err(AuthError::DelegationNotFound {
211                tree_id: root_id.to_string(),
212            }
213            .into()),
214        }
215    }
216
217    /// Get all delegated tree references
218    ///
219    /// Returns a map from root tree ID to the delegation reference.
220    pub fn get_all_delegated_trees(&self) -> Result<HashMap<ID, DelegatedTreeRef>> {
221        let mut result: HashMap<ID, DelegatedTreeRef> = HashMap::new();
222
223        // Get the "delegations" sub-doc
224        if let Some(Value::Doc(delegations_doc)) = self.inner.get("delegations") {
225            for (root_id_str, value) in delegations_doc.iter() {
226                if let Value::Doc(doc) = value
227                    && let Ok(tree_ref) = DelegatedTreeRef::try_from(doc)
228                {
229                    let root_id = ID::new(root_id_str);
230                    result.insert(root_id, tree_ref);
231                }
232            }
233        }
234
235        Ok(result)
236    }
237
238    // ==================== Global Permission ====================
239
240    /// Set the global permission
241    ///
242    /// Stores the global permission at the `global` path, separate from
243    /// individual key entries in the `keys` namespace.
244    pub fn set_global_permission(&mut self, key: AuthKey) {
245        self.inner.set("global", key);
246    }
247
248    /// Get the global permission AuthKey
249    ///
250    /// Reads from the `global` path.
251    pub fn get_global_key(&self) -> Result<AuthKey> {
252        match self.inner.get("global") {
253            Some(Value::Doc(doc)) => AuthKey::try_from(doc).map_err(|e| {
254                AuthError::InvalidKeyFormat {
255                    reason: e.to_string(),
256                }
257                .into()
258            }),
259            Some(_) => Err(AuthError::InvalidKeyFormat {
260                reason: "global key is not a Doc".to_string(),
261            }
262            .into()),
263            None => Err(AuthError::KeyNotFound {
264                key_name: "global".to_string(),
265            }
266            .into()),
267        }
268    }
269
270    // ==================== Key Hint Resolution ====================
271
272    /// Resolve a key hint to matching authentication info
273    ///
274    /// Returns Vec of ResolvedAuth. For pubkey hints, returns at most one.
275    /// For name hints, may return multiple if names collide. Caller should try each
276    /// until signature verifies.
277    ///
278    /// # Name Collision Handling
279    ///
280    /// When multiple keys share the same name, all matching keys are returned.
281    /// The caller (typically `validate_entry`) should iterate through the matches
282    /// and attempt signature verification with each until one succeeds.
283    pub fn resolve_hint(&self, hint: &KeyHint) -> Result<Vec<ResolvedAuth>> {
284        // Handle global permission
285        if hint.is_global() {
286            // Global hint - check that global permission exists
287            let global_key =
288                self.get_global_key()
289                    .map_err(|_| AuthError::InvalidAuthConfiguration {
290                        reason: "Global hint used but no global permission configured".to_string(),
291                    })?;
292
293            // The actual pubkey is directly in hint.pubkey
294            let actual_pubkey =
295                hint.pubkey
296                    .as_ref()
297                    .ok_or_else(|| AuthError::InvalidAuthConfiguration {
298                        reason: "Global hint has no pubkey".to_string(),
299                    })?;
300
301            // Return ResolvedAuth with actual pubkey and global permission
302            // There is only 1 global, no need to look for others
303            return Ok(vec![ResolvedAuth {
304                public_key: actual_pubkey.clone(),
305                effective_permission: *global_key.permissions(),
306                key_status: global_key.status().clone(),
307            }]);
308        }
309
310        // Direct pubkey lookup
311        if let Some(pubkey) = &hint.pubkey {
312            return match self.get_key_by_pubkey(pubkey) {
313                Ok(key) => Ok(vec![ResolvedAuth {
314                    public_key: pubkey.clone(),
315                    effective_permission: *key.permissions(),
316                    key_status: key.status().clone(),
317                }]),
318                Err(e) => Err(e),
319            };
320        }
321
322        // Name lookup - may return multiple matches
323        if let Some(name) = &hint.name {
324            let matches = self.find_keys_by_name(name);
325            if matches.is_empty() {
326                return Err(AuthError::KeyNotFound {
327                    key_name: name.clone(),
328                }
329                .into());
330            }
331            // Convert all matches to ResolvedAuth
332            let mut results = Vec::with_capacity(matches.len());
333            for (pubkey, auth_key) in matches {
334                results.push(ResolvedAuth {
335                    public_key: PublicKey::from_prefixed_string(&pubkey)?,
336                    effective_permission: *auth_key.permissions(),
337                    key_status: auth_key.status().clone(),
338                });
339            }
340            return Ok(results);
341        }
342
343        // No hint set - empty/unsigned
344        Ok(vec![])
345    }
346
347    // ==================== Permission Helpers ====================
348
349    /// Check if global permission exists and is active
350    pub fn has_global_permission(&self) -> bool {
351        self.get_global_permission().is_some()
352    }
353
354    /// Get global permission level if it exists and is active
355    pub fn get_global_permission(&self) -> Option<Permission> {
356        if let Ok(key) = self.get_global_key()
357            && *key.status() == KeyStatus::Active
358        {
359            Some(*key.permissions())
360        } else {
361            None
362        }
363    }
364
365    /// Check if global permission grants sufficient access
366    pub fn global_permission_grants_access(&self, requested_permission: &Permission) -> bool {
367        if let Some(global_perm) = self.get_global_permission() {
368            global_perm >= *requested_permission
369        } else {
370            false
371        }
372    }
373
374    // ==================== Access Control ====================
375
376    /// Check if a public key can access the database with the requested permission
377    pub fn can_access(&self, pubkey: &PublicKey, requested_permission: &Permission) -> bool {
378        // First check if there's a specific key entry for this pubkey
379        if let Ok(auth_key) = self.get_key_by_pubkey(pubkey)
380            && *auth_key.status() == KeyStatus::Active
381            && *auth_key.permissions() >= *requested_permission
382        {
383            return true;
384        }
385
386        // Check global permission
387        self.global_permission_grants_access(requested_permission)
388    }
389
390    /// Find all SigKeys that a public key can use to access this database
391    ///
392    /// Returns (SigKey, Permission) tuples sorted by permission (highest first)
393    pub fn find_all_sigkeys_for_pubkey(&self, pubkey: &PublicKey) -> Vec<(SigKey, Permission)> {
394        let mut results = Vec::new();
395
396        // Check if this pubkey has a direct key entry
397        if let Ok(auth_key) = self.get_key_by_pubkey(pubkey) {
398            results.push((SigKey::from_pubkey(pubkey), *auth_key.permissions()));
399        }
400
401        // Check if global permission exists
402        if let Some(global_perm) = self.get_global_permission() {
403            results.push((SigKey::global(pubkey), global_perm));
404        }
405
406        // Note: Delegation path discovery happens at the Database::find_sigkeys() level
407        // because it requires async Instance access to load delegated tree auth settings.
408
409        // Sort by permission, highest first (reverse sort since Permission Ord has higher > lower)
410        results.sort_by(|a, b| b.1.cmp(&a.1));
411        results
412    }
413
414    /// Resolve which SigKey should be used for an operation
415    ///
416    /// Returns the SigKey with highest permission for the given pubkey.
417    pub fn resolve_sig_key_for_operation(
418        &self,
419        pubkey: &PublicKey,
420    ) -> Result<(SigKey, Permission)> {
421        let matches = self.find_all_sigkeys_for_pubkey(pubkey);
422
423        matches.into_iter().next().ok_or_else(|| {
424            AuthError::PermissionDenied {
425                reason: format!("No active key found for pubkey: {pubkey}"),
426            }
427            .into()
428        })
429    }
430
431    // ==================== Key Modification Authorization ====================
432
433    /// Check if a signing key can modify an existing target key
434    pub fn can_modify_key(
435        &self,
436        signing_key: &ResolvedAuth,
437        target_pubkey: &PublicKey,
438    ) -> Result<bool> {
439        // Must have admin permissions to modify keys
440        if !signing_key.effective_permission.can_admin() {
441            return Ok(false);
442        }
443
444        // Get target key info
445        let target_key = self.get_key_by_pubkey(target_pubkey)?;
446
447        // Signing key must be >= target key permissions
448        Ok(signing_key.effective_permission >= *target_key.permissions())
449    }
450
451    /// Check if a signing key can create a new key with the specified permissions
452    pub fn can_create_key(
453        &self,
454        signing_key: &ResolvedAuth,
455        new_key_permissions: &Permission,
456    ) -> Result<bool> {
457        // Must have admin permissions to create keys
458        if !signing_key.effective_permission.can_admin() {
459            return Ok(false);
460        }
461
462        // Signing key must be >= new key permissions
463        Ok(signing_key.effective_permission >= *new_key_permissions)
464    }
465}
466
467impl Default for AuthSettings {
468    fn default() -> Self {
469        Self::new()
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476    use crate::crdt::CRDT;
477
478    #[test]
479    fn test_auth_settings_basic_operations() {
480        let mut settings = AuthSettings::new();
481
482        let pubkey = PublicKey::random();
483        let auth_key = AuthKey::active(Some("laptop"), Permission::Write(10));
484
485        settings.add_key(&pubkey, auth_key.clone()).unwrap();
486
487        // Retrieve the key
488        let retrieved = settings.get_key_by_pubkey(&pubkey).unwrap();
489        assert_eq!(retrieved.name(), Some("laptop"));
490        assert_eq!(retrieved.permissions(), auth_key.permissions());
491        assert_eq!(retrieved.status(), auth_key.status());
492    }
493
494    #[test]
495    fn test_find_keys_by_name() {
496        let mut settings = AuthSettings::new();
497
498        let pubkey1 = PublicKey::random();
499        let pubkey2 = PublicKey::random();
500
501        // Add two keys with same name
502        settings
503            .add_key(
504                &pubkey1,
505                AuthKey::active(Some("device"), Permission::Write(10)),
506            )
507            .unwrap();
508        settings
509            .add_key(
510                &pubkey2,
511                AuthKey::active(Some("device"), Permission::Admin(1)),
512            )
513            .unwrap();
514
515        // Find by name should return both
516        let matches = settings.find_keys_by_name("device");
517        assert_eq!(matches.len(), 2);
518    }
519
520    #[test]
521    fn test_revoke_key() {
522        let mut settings = AuthSettings::new();
523
524        let pubkey = PublicKey::random();
525        let auth_key = AuthKey::active(Some("laptop"), Permission::Admin(5));
526
527        settings.add_key(&pubkey, auth_key).unwrap();
528
529        // Revoke the key
530        settings.revoke_key(&pubkey).unwrap();
531
532        // Check that it's revoked
533        let retrieved = settings.get_key_by_pubkey(&pubkey).unwrap();
534        assert_eq!(retrieved.status(), &KeyStatus::Revoked);
535    }
536
537    #[test]
538    fn test_global_permission() {
539        let mut settings = AuthSettings::new();
540
541        // No global permission initially
542        assert!(!settings.has_global_permission());
543        assert_eq!(settings.get_global_permission(), None);
544
545        // Set global Write(10) permission
546        let global_key = AuthKey::active(None, Permission::Write(10));
547        settings.set_global_permission(global_key);
548
549        // Global permission should now be detected
550        assert!(settings.has_global_permission());
551        assert_eq!(
552            settings.get_global_permission(),
553            Some(Permission::Write(10))
554        );
555
556        // Test permission granting
557        assert!(settings.global_permission_grants_access(&Permission::Read));
558        assert!(settings.global_permission_grants_access(&Permission::Write(10)));
559        assert!(!settings.global_permission_grants_access(&Permission::Write(5)));
560        assert!(!settings.global_permission_grants_access(&Permission::Admin(10)));
561    }
562
563    #[test]
564    fn test_resolve_hint_pubkey() {
565        let mut settings = AuthSettings::new();
566
567        let pubkey = PublicKey::random();
568        settings
569            .add_key(
570                &pubkey,
571                AuthKey::active(Some("laptop"), Permission::Write(10)),
572            )
573            .unwrap();
574
575        // Resolve by pubkey hint
576        let hint = KeyHint::from_pubkey(&pubkey);
577        let matches = settings.resolve_hint(&hint).unwrap();
578        assert_eq!(matches.len(), 1);
579        assert_eq!(matches[0].public_key, pubkey);
580        assert_eq!(matches[0].effective_permission, Permission::Write(10));
581    }
582
583    #[test]
584    fn test_resolve_hint_name() {
585        let mut settings = AuthSettings::new();
586
587        let pubkey = PublicKey::random();
588        settings
589            .add_key(
590                &pubkey,
591                AuthKey::active(Some("laptop"), Permission::Write(10)),
592            )
593            .unwrap();
594
595        // Resolve by name hint
596        let hint = KeyHint::from_name("laptop");
597        let matches = settings.resolve_hint(&hint).unwrap();
598        assert_eq!(matches.len(), 1);
599        assert_eq!(matches[0].public_key, pubkey);
600        assert_eq!(matches[0].effective_permission, Permission::Write(10));
601    }
602
603    #[test]
604    fn test_resolve_hint_global() {
605        let mut settings = AuthSettings::new();
606
607        // Set global permission
608        settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
609
610        let actual_pubkey = PublicKey::random();
611        let hint = KeyHint::global(&actual_pubkey);
612        let matches = settings.resolve_hint(&hint).unwrap();
613
614        assert_eq!(matches.len(), 1);
615        assert_eq!(matches[0].public_key, actual_pubkey);
616        assert_eq!(matches[0].effective_permission, Permission::Write(10));
617    }
618
619    #[test]
620    fn test_find_all_sigkeys_for_pubkey() {
621        let mut settings = AuthSettings::new();
622
623        let pubkey = PublicKey::random();
624
625        // No keys - should return empty vec
626        let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
627        assert_eq!(results.len(), 0);
628
629        // Add direct key
630        settings
631            .add_key(
632                &pubkey,
633                AuthKey::active(Some("device1"), Permission::Write(5)),
634            )
635            .unwrap();
636
637        let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
638        assert_eq!(results.len(), 1);
639
640        // Set global permission
641        settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
642
643        let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
644        assert_eq!(results.len(), 2);
645    }
646
647    #[test]
648    fn test_resolve_sig_key_for_operation() {
649        let mut settings = AuthSettings::new();
650
651        let pubkey = PublicKey::random();
652
653        // No keys configured - should fail
654        let result = settings.resolve_sig_key_for_operation(&pubkey);
655        assert!(result.is_err());
656
657        // Add device key
658        settings
659            .add_key(
660                &pubkey,
661                AuthKey::active(Some("device"), Permission::Write(5)),
662            )
663            .unwrap();
664
665        // Should resolve to direct pubkey
666        let (sig_key, granted_perm) = settings.resolve_sig_key_for_operation(&pubkey).unwrap();
667        assert!(sig_key.has_pubkey_hint(&pubkey));
668        assert_eq!(granted_perm, Permission::Write(5));
669    }
670
671    #[test]
672    fn test_can_access() {
673        let mut settings = AuthSettings::new();
674
675        let pubkey = PublicKey::random();
676        let other_pubkey = PublicKey::random();
677
678        // No access without keys
679        assert!(!settings.can_access(&pubkey, &Permission::Read));
680
681        // Add specific key
682        settings
683            .add_key(
684                &pubkey,
685                AuthKey::active(Some("device"), Permission::Write(5)),
686            )
687            .unwrap();
688
689        // Specific key should have access
690        assert!(settings.can_access(&pubkey, &Permission::Read));
691        assert!(settings.can_access(&pubkey, &Permission::Write(5)));
692        assert!(!settings.can_access(&pubkey, &Permission::Admin(1)));
693
694        // Other key should not have access
695        assert!(!settings.can_access(&other_pubkey, &Permission::Read));
696
697        // Set global permission
698        settings.set_global_permission(AuthKey::active(None, Permission::Read));
699
700        // Other key should now have read access via global
701        assert!(settings.can_access(&other_pubkey, &Permission::Read));
702        assert!(!settings.can_access(&other_pubkey, &Permission::Write(10)));
703    }
704
705    #[test]
706    fn test_auth_settings_merge() {
707        let mut settings1 = AuthSettings::new();
708        let mut settings2 = AuthSettings::new();
709
710        let pubkey1 = PublicKey::random();
711        let pubkey2 = PublicKey::random();
712
713        settings1
714            .add_key(
715                &pubkey1,
716                AuthKey::active(Some("key1"), Permission::Write(10)),
717            )
718            .unwrap();
719        settings2
720            .add_key(
721                &pubkey2,
722                AuthKey::active(Some("key2"), Permission::Admin(5)),
723            )
724            .unwrap();
725
726        // Merge at Doc level
727        let merged_doc = settings1.as_doc().merge(settings2.as_doc()).unwrap();
728        let merged_settings: AuthSettings = merged_doc.into();
729
730        // Both keys should be present
731        assert!(merged_settings.get_key_by_pubkey(&pubkey1).is_ok());
732        assert!(merged_settings.get_key_by_pubkey(&pubkey2).is_ok());
733    }
734}