sos_client_storage/
traits.rs

1//! Client storage implementations.
2use crate::{
3    AccessOptions, AccountPack, Error, NewFolderOptions, Result,
4    StorageChangeEvent,
5};
6use async_trait::async_trait;
7use futures::{pin_mut, StreamExt};
8use indexmap::IndexSet;
9use sos_backend::{
10    compact::compact_folder, extract_vault, BackendTarget, DeviceEventLog,
11    Folder, FolderEventLog,
12};
13use sos_core::{
14    commit::{CommitHash, CommitState},
15    crypto::AccessKey,
16    decode,
17    device::{DevicePublicKey, TrustedDevice},
18    encode,
19    events::{
20        patch::FolderPatch, AccountEvent, DeviceEvent, Event, EventKind,
21        EventLog, EventRecord, ReadEvent, WriteEvent,
22    },
23    AccountId, AuthenticationError, FolderRef, Paths, SecretId, StorageError,
24    UtcDateTime, VaultCommit, VaultFlags, VaultId,
25};
26use sos_login::{DelegatedAccess, FolderKeys, Identity};
27use sos_password::diceware::generate_passphrase;
28use sos_reducers::{DeviceReducer, FolderReducer};
29use sos_sync::{CreateSet, StorageEventLogs};
30use sos_vault::{
31    secret::{Secret, SecretMeta, SecretRow},
32    BuilderCredentials, ChangePassword, SecretAccess, Summary, Vault,
33    VaultBuilder,
34};
35use std::{collections::HashMap, sync::Arc};
36use tokio::sync::RwLock;
37
38#[cfg(feature = "search")]
39use sos_search::{AccountSearch, DocumentCount};
40
41#[cfg(feature = "audit")]
42use {
43    sos_audit::{AuditData, AuditEvent},
44    sos_backend::audit::append_audit_events,
45};
46
47#[cfg(feature = "files")]
48use {crate::files::ExternalFileManager, sos_backend::FileEventLog};
49
50pub(crate) mod private {
51    /// Internal struct for sealed functions.
52    #[derive(Copy, Clone)]
53    pub struct Internal;
54}
55
56use private::Internal;
57
58/// Common functions for all client storage traits.
59pub trait ClientBaseStorage {
60    /// Account identifier.
61    fn account_id(&self) -> &AccountId;
62
63    /// Authenticated user information.
64    fn authenticated_user(&self) -> Option<&Identity>;
65
66    /// Mutable authenticated user information.
67    fn authenticated_user_mut(&mut self) -> Option<&mut Identity>;
68
69    /// Determine if the storage is authenticated.
70    fn is_authenticated(&self) -> bool {
71        self.authenticated_user().is_some()
72    }
73
74    /// Computed storage paths.
75    fn paths(&self) -> Arc<Paths>;
76
77    /// Backend target.
78    fn backend_target(&self) -> &BackendTarget;
79
80    /// Set the authenticated user.
81    #[doc(hidden)]
82    fn set_authenticated_user(&mut self, user: Option<Identity>, _: Internal);
83
84    #[doc(hidden)]
85    fn guard_authenticated(&self, _: Internal) -> Result<()> {
86        if self.authenticated_user().is_none() {
87            // panic!("not authenticated");
88            return Err(AuthenticationError::NotAuthenticated.into());
89        }
90        Ok(())
91    }
92}
93
94/// Device management for client storage.
95#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
96#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
97pub trait ClientDeviceStorage:
98    StorageEventLogs<Error = Error> + ClientBaseStorage
99{
100    /// Collection of trusted devices.
101    fn devices(&self) -> &IndexSet<TrustedDevice>;
102
103    /// Set the collection of trusted devices.
104    #[doc(hidden)]
105    fn set_devices(&mut self, devices: IndexSet<TrustedDevice>, _: Internal);
106
107    /// List trusted devices.
108    fn list_trusted_devices(&self) -> Vec<&TrustedDevice>;
109
110    /// Patch the devices event log.
111    async fn patch_devices_unchecked(
112        &mut self,
113        events: &[DeviceEvent],
114    ) -> Result<()> {
115        self.guard_authenticated(Internal)?;
116
117        // Update the event log
118        let device_log = self.device_log().await?;
119        let mut event_log = device_log.write().await;
120        event_log.apply(events).await?;
121
122        // Update in-memory cache of trusted devices
123        let reducer = DeviceReducer::new(&*event_log);
124        let devices = reducer.reduce().await?;
125        self.set_devices(devices, Internal);
126
127        #[cfg(feature = "audit")]
128        {
129            let audit_events = events
130                .iter()
131                .filter_map(|event| match event {
132                    DeviceEvent::Trust(device) => Some(AuditEvent::new(
133                        Default::default(),
134                        EventKind::TrustDevice,
135                        *self.account_id(),
136                        Some(AuditData::Device(*device.public_key())),
137                    )),
138                    _ => None,
139                })
140                .collect::<Vec<_>>();
141            if !audit_events.is_empty() {
142                append_audit_events(audit_events.as_slice()).await?;
143            }
144        }
145
146        Ok(())
147    }
148
149    /// Revoke trust in a device.
150    async fn revoke_device(
151        &mut self,
152        public_key: &DevicePublicKey,
153    ) -> Result<()> {
154        let device =
155            self.devices().iter().find(|d| d.public_key() == public_key);
156        if device.is_some() {
157            let event = DeviceEvent::Revoke(*public_key);
158
159            let device_log = self.device_log().await?;
160            let mut writer = device_log.write().await;
161            writer.apply(&[event]).await?;
162
163            let reducer = DeviceReducer::new(&*writer);
164            self.set_devices(reducer.reduce().await?, Internal);
165        }
166
167        Ok(())
168    }
169}
170
171/// Vault management for client storage.
172#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
173#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
174pub trait ClientVaultStorage {
175    /// Write a vault to storage.
176    #[doc(hidden)]
177    async fn write_vault(
178        &self,
179        vault: &Vault,
180        _: Internal,
181    ) -> Result<Vec<u8>>;
182
183    /// Write the login vault to storage.
184    #[doc(hidden)]
185    async fn write_login_vault(
186        &self,
187        vault: &Vault,
188        _: Internal,
189    ) -> Result<Vec<u8>>;
190
191    /// Remove a vault.
192    #[doc(hidden)]
193    async fn remove_vault(
194        &self,
195        folder_id: &VaultId,
196        _: Internal,
197    ) -> Result<()>;
198
199    /// Read folders from the storage.
200    #[doc(hidden)]
201    async fn read_vaults(&self, _: Internal) -> Result<Vec<Summary>>;
202
203    /// In-memory collection of folder summaries
204    /// managed by this storage.
205    #[doc(hidden)]
206    fn summaries(&self, _: Internal) -> &Vec<Summary>;
207
208    /// Mutable in-memory collection of folder summaries
209    /// managed by this storage.
210    #[doc(hidden)]
211    fn summaries_mut(&mut self, _: Internal) -> &mut Vec<Summary>;
212
213    /// Add a summary to the in-memory storage.
214    #[doc(hidden)]
215    fn add_summary(&mut self, summary: Summary, token: Internal) {
216        let summaries = self.summaries_mut(token);
217        summaries.push(summary);
218        summaries.sort();
219    }
220
221    /// Remove a summary from the in-memory storage.
222    #[doc(hidden)]
223    fn remove_summary(&mut self, folder_id: &VaultId, token: Internal) {
224        if let Some(position) = self
225            .summaries(token)
226            .iter()
227            .position(|s| s.id() == folder_id)
228        {
229            self.summaries_mut(token).remove(position);
230            self.summaries_mut(token).sort();
231        }
232    }
233}
234
235/// Folder management for client storage.
236#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
237#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
238pub trait ClientFolderStorage:
239    StorageEventLogs<Error = Error> + ClientBaseStorage + ClientVaultStorage
240{
241    /// In-memory folders.
242    fn folders(&self) -> &HashMap<VaultId, Folder>;
243
244    /// Mutable in-memory folders.
245    fn folders_mut(&mut self) -> &mut HashMap<VaultId, Folder>;
246
247    /// Create a new folder.
248    #[doc(hidden)]
249    async fn new_folder(&self, vault: &Vault, _: Internal) -> Result<Folder>;
250
251    /// Read a vault from the storage.
252    async fn read_vault(&self, id: &VaultId) -> Result<Vault>;
253
254    /// Read the login vault from the storage.
255    async fn read_login_vault(&self) -> Result<Vault>;
256
257    /// Read the device vault from the storage.
258    async fn read_device_vault(&self) -> Result<Option<Vault>> {
259        Ok(self
260            .backend_target()
261            .read_device_vault(self.account_id())
262            .await?)
263    }
264
265    /// List the in-memory folders.
266    fn list_folders(&self) -> &[Summary] {
267        self.summaries(Internal).as_slice()
268    }
269
270    /// Currently open folder.
271    fn current_folder(&self) -> Option<Summary>;
272
273    /// Find a folder in this storage by reference.
274    fn find_folder(&self, vault: &FolderRef) -> Option<&Summary> {
275        match vault {
276            FolderRef::Name(name) => {
277                self.summaries(Internal).iter().find(|s| s.name() == name)
278            }
279            FolderRef::Id(id) => {
280                self.summaries(Internal).iter().find(|s| s.id() == id)
281            }
282        }
283    }
284
285    /// Find a folder in this storage using a predicate.
286    fn find<F>(&self, predicate: F) -> Option<&Summary>
287    where
288        F: FnMut(&&Summary) -> bool,
289    {
290        self.summaries(Internal).iter().find(predicate)
291    }
292
293    /// Initialize a folder from a collection of event records.
294    ///
295    /// If an event log exists for the folder identifer
296    /// it is replaced with the new event records.
297    ///
298    /// A vault is created from the reduced collection of events
299    /// and written to storage.
300    #[doc(hidden)]
301    async fn initialize_folder(
302        &mut self,
303        // folder_id: &VaultId,
304        records: Vec<EventRecord>,
305        _: Internal,
306    ) -> Result<(Folder, Vault)> {
307        // Prepare the vault
308        let vault = {
309            let vault = extract_vault(records.as_slice())
310                .await?
311                .ok_or(Error::NoVaultEvent)?;
312
313            let folder = self.new_folder(&vault, Internal).await?;
314            let event_log = folder.event_log();
315            let mut event_log = event_log.write().await;
316            event_log.clear().await?;
317            event_log.apply_records(records).await?;
318
319            let vault = FolderReducer::new()
320                .reduce(&*event_log)
321                .await?
322                .build(true)
323                .await?;
324
325            self.write_vault(&vault, Internal).await?;
326
327            vault
328        };
329
330        // NOTE: we should be able to just return the folder and vault
331        // NOTE: above and not re-load here but this breaks the
332        // NOTE: network_sync_recover_remote_folder test so we need
333        // NOTE: to figure out why
334
335        // Setup the folder access to the latest vault information
336        // and load the merkle tree
337        let folder = self.new_folder(&vault, Internal).await?;
338        let event_log = folder.event_log();
339        let mut event_log = event_log.write().await;
340        event_log.load_tree().await?;
341
342        Ok((folder, vault))
343    }
344
345    /// Create new in-memory folder entry.
346    #[doc(hidden)]
347    async fn create_folder_entry(
348        &mut self,
349        vault: Vault,
350        reset_events: bool,
351        creation_time: Option<&UtcDateTime>,
352        _: Internal,
353    ) -> Result<()> {
354        let folder_id = *vault.id();
355        let mut folder = self.new_folder(&vault, Internal).await?;
356
357        // Reset the event log from the contents of the vault.
358        //
359        // Used when importing or upserting from vaults to overwrite
360        // and existing event logs.
361        if reset_events {
362            let (_, events) = FolderReducer::split::<Error>(vault).await?;
363            let mut records = Vec::with_capacity(events.len());
364            for event in events.iter() {
365                records.push(EventRecord::encode_event(event).await?);
366            }
367            if let (Some(creation_time), Some(event)) =
368                (creation_time, records.get_mut(0))
369            {
370                event.set_time(creation_time.to_owned());
371            }
372
373            // Must truncate the event log so that importing vaults
374            // does not end up with multiple create vault events
375            folder.clear().await?;
376            folder.apply_records(records).await?;
377        }
378
379        self.folders_mut().insert(folder_id, folder);
380
381        Ok(())
382    }
383
384    /// Create a cache entry for each summary if it does not
385    /// already exist.
386    #[doc(hidden)]
387    async fn load_caches(
388        &mut self,
389        folders: &[Summary],
390        _: Internal,
391    ) -> Result<()> {
392        for folder in folders {
393            // Ensure we don't overwrite existing data
394            let folder_id = folder.id();
395            if self.folders().get(folder_id).is_none() {
396                let folder = Folder::new(
397                    self.backend_target().clone(),
398                    self.account_id(),
399                    folder.id(),
400                )
401                .await?;
402                self.folders_mut().insert(*folder_id, folder);
403            }
404        }
405        Ok(())
406    }
407
408    /// Remove the local cache for a vault.
409    #[doc(hidden)]
410    fn remove_folder_entry(
411        &mut self,
412        folder_id: &VaultId,
413        _: Internal,
414    ) -> Result<()> {
415        let current_id = self.current_folder().map(|c| *c.id());
416
417        // If the deleted vault is the currently selected
418        // vault we must close it
419        if let Some(id) = &current_id {
420            if id == folder_id {
421                self.close_folder();
422            }
423        }
424
425        // Remove from our cache of managed vaults
426        self.folders_mut().remove(folder_id);
427
428        // Remove from the state of managed vaults
429        self.remove_summary(folder_id, Internal);
430
431        Ok(())
432    }
433
434    /// Update an existing vault by replacing it with a new vault.
435    async fn update_vault(
436        &mut self,
437        vault: &Vault,
438        events: Vec<WriteEvent>,
439    ) -> Result<Vec<u8>> {
440        self.guard_authenticated(Internal)?;
441
442        let buffer = self.write_vault(vault, Internal).await?;
443
444        // Apply events to the event log
445        let folder = self
446            .folders_mut()
447            .get_mut(vault.id())
448            .ok_or(StorageError::FolderNotFound(*vault.id()))?;
449        folder.clear().await?;
450        folder.apply(events.as_slice()).await?;
451
452        Ok(buffer)
453    }
454
455    /// Load a vault by reducing it from the event log stored on disc.
456    #[doc(hidden)]
457    async fn reduce_event_log(
458        &mut self,
459        folder_id: &VaultId,
460        _: Internal,
461    ) -> Result<Vault> {
462        let event_log = self.folder_log(folder_id).await?;
463        let log_file = event_log.read().await;
464        Ok(FolderReducer::new()
465            .reduce(&*log_file)
466            .await?
467            .build(true)
468            .await?)
469    }
470
471    /// Refresh the in-memory vault from the contents
472    /// of the current event log file.
473    ///
474    /// If a new access key is given and then the
475    /// in-memory `AccessPoint` is updated to use the new
476    /// access key.
477    #[doc(hidden)]
478    async fn refresh_vault(
479        &mut self,
480        folder_id: &VaultId,
481        key: &AccessKey,
482        _: Internal,
483    ) -> Result<Vec<u8>> {
484        let vault = self.reduce_event_log(folder_id, Internal).await?;
485        let buffer = self.write_vault(&vault, Internal).await?;
486
487        if let Some(folder) = self.folders_mut().get_mut(folder_id) {
488            let access_point = folder.access_point();
489            let mut access_point = access_point.lock().await;
490
491            access_point.lock();
492            access_point.replace_vault(vault.clone(), false).await?;
493            access_point.unlock(key).await?;
494        }
495
496        Ok(buffer)
497    }
498
499    /// Read folders from storage and create the in-memory
500    /// event logs for each folder.
501    async fn load_folders(&mut self) -> Result<&[Summary]> {
502        let summaries = self.read_vaults(Internal).await?;
503        self.load_caches(&summaries, Internal).await?;
504        *self.summaries_mut(Internal) = summaries;
505        Ok(self.list_folders())
506    }
507
508    /// Remove a folder from memory.
509    async fn remove_folder(&mut self, folder_id: &VaultId) -> Result<bool> {
510        self.guard_authenticated(Internal)?;
511
512        Ok(if self.find(|s| s.id() == folder_id).is_some() {
513            self.remove_folder_entry(folder_id, Internal)?;
514            true
515        } else {
516            false
517        })
518    }
519
520    /// Mark a folder as the currently open folder.
521    fn open_folder(&self, folder_id: &VaultId) -> Result<ReadEvent>;
522
523    /// Close the current open folder.
524    fn close_folder(&self);
525
526    /// Create folders from a collection of folder patches.
527    ///
528    /// If the folders already exist they will be overwritten.
529    async fn import_folder_patches(
530        &mut self,
531        patches: HashMap<VaultId, FolderPatch>,
532    ) -> Result<()> {
533        self.guard_authenticated(Internal)?;
534
535        for (folder_id, patch) in patches {
536            let records: Vec<EventRecord> = patch.into();
537            let (folder, vault) =
538                self.initialize_folder(records, Internal).await?;
539
540            {
541                let event_log = folder.event_log();
542                let event_log = event_log.read().await;
543                tracing::info!(
544                  folder_id = %folder_id,
545                  root = ?event_log.tree().root().map(|c| c.to_string()),
546                  "import_folder_patch");
547            }
548
549            self.folders_mut().insert(folder_id, folder);
550            let summary = vault.summary().to_owned();
551            self.remove_summary(summary.id(), Internal);
552            self.add_summary(summary, Internal);
553        }
554        Ok(())
555    }
556
557    /// Compact an event log file.
558    async fn compact_folder(
559        &mut self,
560        folder_id: &VaultId,
561        key: &AccessKey,
562    ) -> Result<AccountEvent> {
563        self.guard_authenticated(Internal)?;
564
565        {
566            let folder = self
567                .folders_mut()
568                .get_mut(folder_id)
569                .ok_or(StorageError::FolderNotFound(*folder_id))?;
570            let event_log = folder.event_log();
571            let mut log_file = event_log.write().await;
572
573            compact_folder(self.account_id(), folder_id, &mut *log_file)
574                .await?;
575        }
576
577        // Refresh in-memory vault and mirrored copy
578        let buffer = self.refresh_vault(folder_id, key, Internal).await?;
579
580        let account_event = AccountEvent::CompactFolder(*folder_id, buffer);
581
582        let account_log = self.account_log().await?;
583        let mut account_log = account_log.write().await;
584        account_log.apply(&[account_event.clone()]).await?;
585
586        Ok(account_event)
587    }
588
589    /// Set the name of a folder.
590    async fn rename_folder(
591        &mut self,
592        folder_id: &VaultId,
593        name: impl AsRef<str> + Send,
594    ) -> Result<Event> {
595        self.guard_authenticated(Internal)?;
596
597        // Update the in-memory name.
598        self.set_folder_name(folder_id, name.as_ref(), Internal)?;
599
600        let folder = self
601            .folders_mut()
602            .get_mut(folder_id)
603            .ok_or(StorageError::FolderNotFound(*folder_id))?;
604
605        folder.rename_folder(name.as_ref()).await?;
606
607        let account_event =
608            AccountEvent::RenameFolder(*folder_id, name.as_ref().to_owned());
609
610        let account_log = self.account_log().await?;
611        let mut account_log = account_log.write().await;
612        account_log.apply(&[account_event.clone()]).await?;
613
614        #[cfg(feature = "audit")]
615        {
616            let audit_event: AuditEvent =
617                (self.account_id(), &account_event).into();
618            append_audit_events(&[audit_event]).await?;
619        }
620
621        Ok(Event::Account(account_event))
622    }
623
624    /// Update the flags for a folder.
625    async fn update_folder_flags(
626        &mut self,
627        folder_id: &VaultId,
628        flags: VaultFlags,
629    ) -> Result<Event> {
630        self.guard_authenticated(Internal)?;
631
632        // Update the in-memory name.
633        self.set_folder_flags(folder_id, flags.clone(), Internal)?;
634
635        let folder = self
636            .folders_mut()
637            .get_mut(folder_id)
638            .ok_or(StorageError::FolderNotFound(*folder_id))?;
639
640        let event = folder.update_folder_flags(flags).await?;
641        let event = Event::Write(*folder_id, event);
642
643        #[cfg(feature = "audit")]
644        {
645            let audit_event: AuditEvent = (self.account_id(), &event).into();
646            append_audit_events(&[audit_event]).await?;
647        }
648
649        Ok(event)
650    }
651
652    /// Update the in-memory name for a folder.
653    #[doc(hidden)]
654    fn set_folder_name(
655        &mut self,
656        folder_id: &VaultId,
657        name: impl AsRef<str>,
658        _: Internal,
659    ) -> Result<()> {
660        if let Some(summary) = self
661            .summaries_mut(Internal)
662            .iter_mut()
663            .find(|f| f.id() == folder_id)
664        {
665            summary.set_name(name.as_ref().to_owned());
666        }
667        Ok(())
668    }
669
670    /// Update the in-memory name for a folder.
671    #[doc(hidden)]
672    fn set_folder_flags(
673        &mut self,
674        folder_id: &VaultId,
675        flags: VaultFlags,
676        _: Internal,
677    ) -> Result<()> {
678        if let Some(summary) = self
679            .summaries_mut(Internal)
680            .iter_mut()
681            .find(|f| f.id() == folder_id)
682        {
683            *summary.flags_mut() = flags;
684        }
685        Ok(())
686    }
687
688    /// Get the description for a folder.
689    async fn description(&self, folder_id: &VaultId) -> Result<String> {
690        self.guard_authenticated(Internal)?;
691
692        let folder = self
693            .folders()
694            .get(folder_id)
695            .ok_or_else(|| StorageError::FolderNotFound(*folder_id))?;
696        Ok(folder.description().await?)
697    }
698
699    /// Set the description of the currently open folder.
700    async fn set_description(
701        &mut self,
702        folder_id: &VaultId,
703        description: impl AsRef<str> + Send,
704    ) -> Result<WriteEvent> {
705        self.guard_authenticated(Internal)?;
706
707        let folder = self
708            .folders_mut()
709            .get_mut(folder_id)
710            .ok_or_else(|| StorageError::FolderNotFound(*folder_id))?;
711        Ok(folder.set_description(description).await?)
712    }
713}
714
715/// Secret management for client storage.
716#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
717#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
718pub trait ClientSecretStorage {
719    /// Create a secret in the currently open vault.
720    async fn create_secret(
721        &mut self,
722        secret_data: SecretRow,
723        #[allow(unused_mut, unused_variables)] mut options: AccessOptions,
724    ) -> Result<StorageChangeEvent>;
725
726    /// Read the encrypted contents of a secret.
727    async fn raw_secret(
728        &self,
729        folder_id: &VaultId,
730        secret_id: &SecretId,
731    ) -> Result<Option<(VaultCommit, ReadEvent)>>;
732
733    /// Read a secret in the currently open folder.
734    async fn read_secret(
735        &self,
736        id: &SecretId,
737        options: &AccessOptions,
738    ) -> Result<(Summary, SecretMeta, Secret, ReadEvent)>;
739
740    /// Update a secret in the currently open folder.
741    async fn update_secret(
742        &mut self,
743        secret_id: &SecretId,
744        meta: SecretMeta,
745        secret: Option<Secret>,
746        #[allow(unused_mut, unused_variables)] mut options: AccessOptions,
747    ) -> Result<StorageChangeEvent>;
748
749    /// Write a secret.
750    ///
751    /// Unlike `update_secret()` this function does not support moving
752    /// between folders or managing external files which allows us
753    /// to avoid recursion when handling embedded file secrets which
754    /// require rewriting the secret once the files have been encrypted.
755    #[doc(hidden)]
756    async fn write_secret(
757        &mut self,
758        folder: &Summary,
759        id: &SecretId,
760        mut secret_data: SecretRow,
761        #[allow(unused_variables)] is_update: bool,
762        _: Internal,
763    ) -> Result<WriteEvent>;
764
765    /// Delete a secret in the currently open vault.
766    async fn delete_secret(
767        &mut self,
768        secret_id: &SecretId,
769        #[allow(unused_mut, unused_variables)] mut options: AccessOptions,
770    ) -> Result<StorageChangeEvent>;
771
772    /// Remove a secret.
773    ///
774    /// Any external files for the secret are left intact.
775    async fn remove_secret(
776        &mut self,
777        id: &SecretId,
778        options: &AccessOptions,
779    ) -> Result<WriteEvent>;
780}
781
782/// Internal utility functions for event log management.
783#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
784#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
785#[doc(hidden)]
786pub trait ClientEventLogStorage {
787    /// Initialize the device event log.
788    #[doc(hidden)]
789    async fn initialize_device_log(
790        &self,
791        device: TrustedDevice,
792        _: Internal,
793    ) -> Result<(DeviceEventLog, IndexSet<TrustedDevice>)>;
794
795    /// Initialize the file event log.
796    #[cfg(feature = "files")]
797    #[doc(hidden)]
798    async fn initialize_file_log(&self, _: Internal) -> Result<FileEventLog>;
799
800    /// Set the identity event log.
801    #[doc(hidden)]
802    fn set_identity_log(
803        &mut self,
804        log: Arc<RwLock<FolderEventLog>>,
805        _: Internal,
806    );
807
808    /// Set the device event log.
809    #[doc(hidden)]
810    fn set_device_log(
811        &mut self,
812        log: Arc<RwLock<DeviceEventLog>>,
813        _: Internal,
814    );
815
816    /// Set the file event log.
817    #[cfg(feature = "files")]
818    #[doc(hidden)]
819    fn set_file_log(&mut self, log: Arc<RwLock<FileEventLog>>, _: Internal);
820}
821
822/// Account management for client storage.
823#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
824#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
825pub trait ClientAccountStorage:
826    StorageEventLogs<Error = Error>
827    + ClientBaseStorage
828    + ClientDeviceStorage
829    + ClientFolderStorage
830    + ClientSecretStorage
831    + ClientEventLogStorage
832{
833    /// Rename the account.
834    async fn rename_account(&mut self, account_name: String) -> Result<()> {
835        let authenticated_user = self
836            .authenticated_user_mut()
837            .ok_or(AuthenticationError::NotAuthenticated)?;
838        authenticated_user.rename_account(account_name).await?;
839        Ok(())
840    }
841
842    /// Import an account from a change set of event logs.
843    ///
844    /// Intended to be used during pairing to create a new
845    /// account from a collection of patches.
846    ///
847    /// # Authentication
848    ///
849    /// Can be called when the storage is not authenticated.
850    async fn import_account(
851        &mut self,
852        account_data: &CreateSet,
853    ) -> Result<()>;
854
855    /// List the secret ids for a folder.
856    async fn list_secret_ids(
857        &self,
858        folder_id: &VaultId,
859    ) -> Result<Vec<SecretId>>;
860
861    /// Create a device vault from a buffer.
862    ///
863    /// Used during pairing enrollment to initialize
864    /// a device vault received from the authorizing device.
865    ///
866    /// # Authentication
867    ///
868    /// Can be called when the storage is not authenticated.
869    async fn create_device_vault(
870        &mut self,
871        device_vault: &[u8],
872    ) -> Result<()>;
873
874    /// Delete the account for this user.
875    async fn delete_account(&self) -> Result<Event>;
876
877    /// Set the storage as authenticated.
878    async fn authenticate(
879        &mut self,
880        authenticated_user: Identity,
881    ) -> Result<()> {
882        // Note that attempting to access the identity of an authenticated
883        // user is an error when not authenticated.
884        let identity_log = authenticated_user.identity()?.event_log();
885        let device = authenticated_user
886            .identity()?
887            .devices()?
888            .current_device(None);
889        self.set_identity_log(identity_log, Internal);
890
891        let (device_log, devices) =
892            self.initialize_device_log(device, Internal).await?;
893        self.set_device_log(Arc::new(RwLock::new(device_log)), Internal);
894        self.set_devices(devices, Internal);
895
896        #[cfg(feature = "files")]
897        {
898            let file_log = self.initialize_file_log(Internal).await?;
899            let file_log = Arc::new(RwLock::new(file_log));
900            let file_password =
901                authenticated_user.find_file_encryption_password().await?;
902
903            self.set_external_file_manager(
904                Some(ExternalFileManager::new(
905                    self.paths().clone(),
906                    file_log.clone(),
907                    file_password,
908                )),
909                Internal,
910            );
911            self.set_file_log(file_log, Internal);
912        }
913
914        #[cfg(feature = "search")]
915        {
916            self.set_search_index(Some(AccountSearch::new()), Internal);
917        }
918        self.set_authenticated_user(Some(authenticated_user), Internal);
919
920        Ok(())
921    }
922
923    /// Change the password for a folder.
924    ///
925    /// If the target folder is the currently selected folder
926    /// the currently selected vault is unlocked with the new
927    /// passphrase on success.
928    async fn change_password(
929        &mut self,
930        vault: &Vault,
931        current_key: AccessKey,
932        new_key: AccessKey,
933    ) -> Result<()> {
934        self.guard_authenticated(Internal)?;
935
936        let folder_id = vault.id();
937        let (new_key, new_vault, event_log_events) =
938            ChangePassword::new(vault, current_key, new_key, None)
939                .build()
940                .await?;
941
942        let buffer = self.update_vault(&new_vault, event_log_events).await?;
943
944        let account_event =
945            AccountEvent::ChangeFolderPassword(*folder_id, buffer);
946
947        // Refresh the in-memory and disc-based mirror
948        self.refresh_vault(vault.id(), &new_key, Internal).await?;
949
950        if let Some(folder) = self.folders_mut().get_mut(vault.id()) {
951            let access_point = folder.access_point();
952            let mut access_point = access_point.lock().await;
953            access_point.unlock(&new_key).await?;
954        }
955
956        // Save the new password
957        self.authenticated_user_mut()
958            .ok_or_else(|| AuthenticationError::NotAuthenticated)?
959            .save_folder_password(folder_id, new_key)
960            .await?;
961
962        let account_log = self.account_log().await?;
963        let mut account_log = account_log.write().await;
964        account_log.apply(&[account_event]).await?;
965
966        Ok(())
967    }
968
969    /// Sign out the authenticated user.
970    async fn sign_out(&mut self) -> Result<()> {
971        if let Some(authenticated) = self.authenticated_user_mut() {
972            tracing::debug!("client_storage::sign_out_identity");
973            // Forget private identity information
974            authenticated.sign_out().await?;
975        }
976
977        #[cfg(feature = "search")]
978        {
979            tracing::debug!("client_storage::drop_search_index");
980            self.set_search_index(None, Internal);
981        }
982
983        #[cfg(feature = "files")]
984        {
985            tracing::debug!("client_storage::drop_external_file_manager");
986            self.set_external_file_manager(None, Internal);
987        }
988
989        tracing::debug!("client_storage::drop_authenticated_user");
990        self.set_authenticated_user(None, Internal);
991
992        Ok(())
993    }
994
995    /// Import a login vault and generate the event but
996    /// do not write the event to the account event log.
997    ///
998    /// This is used when merging account event logs to ensure
999    /// the `AccountEvent::UpdateIdentity` event is not duplicated.
1000    ///
1001    /// Typically the handlers that update storage but don't append log
1002    /// events are declared in the storage implementation but the
1003    /// identity log is managed by the account so this must exist here.
1004    #[doc(hidden)]
1005    async fn import_login_vault(
1006        &mut self,
1007        vault: Vault,
1008    ) -> Result<AccountEvent> {
1009        self.guard_authenticated(Internal)?;
1010
1011        let user = self
1012            .authenticated_user()
1013            .ok_or(AuthenticationError::NotAuthenticated)?;
1014
1015        vault
1016            .summary()
1017            .flags()
1018            .contains(VaultFlags::IDENTITY)
1019            .then_some(())
1020            .ok_or_else(|| sos_login::Error::NotIdentityFolder)?;
1021
1022        // Update the identity vault
1023        let buffer = self.write_login_vault(&vault, Internal).await?;
1024
1025        // Update the events for the identity vault
1026        let identity = user.identity()?;
1027        let event_log = identity.event_log();
1028        let mut event_log = event_log.write().await;
1029        event_log.clear().await?;
1030
1031        let (_, events) = FolderReducer::split::<Error>(vault).await?;
1032        event_log.apply(events.as_slice()).await?;
1033
1034        Ok(AccountEvent::UpdateIdentity(buffer))
1035    }
1036
1037    /// Unlock all folders.
1038    async fn unlock(&mut self, keys: &FolderKeys) -> Result<()> {
1039        self.guard_authenticated(Internal)?;
1040
1041        for (id, folder) in self.folders_mut().iter_mut() {
1042            if let Some(key) = keys.find(id) {
1043                folder.unlock(key).await?;
1044            } else {
1045                tracing::error!(
1046                    folder_id = %id,
1047                    "unlock::no_folder_key",
1048                );
1049            }
1050        }
1051        Ok(())
1052    }
1053
1054    /// Lock all folders.
1055    ///
1056    /// # Authentication
1057    ///
1058    /// Can be called when the storage is not authenticated.
1059    async fn lock(&mut self) {
1060        for (_, folder) in self.folders_mut().iter_mut() {
1061            folder.lock().await;
1062        }
1063    }
1064
1065    /// Unlock a folder.
1066    async fn unlock_folder(
1067        &mut self,
1068        folder_id: &VaultId,
1069        key: &AccessKey,
1070    ) -> Result<()> {
1071        self.guard_authenticated(Internal)?;
1072
1073        let folder = self
1074            .folders_mut()
1075            .get_mut(folder_id)
1076            .ok_or(StorageError::FolderNotFound(*folder_id))?;
1077        folder.unlock(key).await?;
1078        Ok(())
1079    }
1080
1081    /// Lock a folder.
1082    ///
1083    /// # Authentication
1084    ///
1085    /// Can be called when the storage is not authenticated.
1086    async fn lock_folder(&mut self, id: &VaultId) -> Result<()> {
1087        let folder = self
1088            .folders_mut()
1089            .get_mut(id)
1090            .ok_or(StorageError::FolderNotFound(*id))?;
1091        folder.lock().await;
1092        Ok(())
1093    }
1094
1095    /// Create the data for a new account.
1096    ///
1097    /// # Authentication
1098    ///
1099    /// Can be called when the storage is not authenticated.
1100    async fn create_account(
1101        &mut self,
1102        account: &AccountPack,
1103    ) -> Result<Vec<Event>> {
1104        let mut events = Vec::new();
1105
1106        let create_account = Event::CreateAccount(account.account_id.into());
1107
1108        // Each folder import will create an audit event
1109        // but we want the create account to be in the audit
1110        // log before the folder create audit events
1111        #[cfg(feature = "audit")]
1112        {
1113            let audit_event: AuditEvent =
1114                (self.account_id(), &create_account).into();
1115            append_audit_events(&[audit_event]).await?;
1116        }
1117
1118        // Import folders
1119        for folder in &account.folders {
1120            let buffer = encode(folder).await?;
1121            let (event, _) =
1122                self.import_folder(buffer, None, true, None).await?;
1123            events.push(event);
1124        }
1125
1126        events.insert(0, create_account);
1127
1128        Ok(events)
1129    }
1130
1131    /// Prepare a new folder.
1132    #[doc(hidden)]
1133    async fn prepare_folder(
1134        &mut self,
1135        mut options: NewFolderOptions,
1136        _: Internal,
1137    ) -> Result<(Vec<u8>, AccessKey, Summary)> {
1138        let key = if let Some(key) = options.key.take() {
1139            key
1140        } else {
1141            let (passphrase, _) = generate_passphrase()?;
1142            AccessKey::Password(passphrase)
1143        };
1144
1145        let builder = VaultBuilder::new()
1146            .flags(options.flags.unwrap_or_default())
1147            .cipher(options.cipher.unwrap_or_default())
1148            .kdf(options.kdf.unwrap_or_default())
1149            .public_name(options.name);
1150
1151        let vault = match &key {
1152            AccessKey::Password(password) => {
1153                builder
1154                    .build(BuilderCredentials::Password(
1155                        password.clone(),
1156                        None,
1157                    ))
1158                    .await?
1159            }
1160            AccessKey::Identity(id) => {
1161                builder
1162                    .build(BuilderCredentials::Shared {
1163                        owner: id,
1164                        recipients: vec![],
1165                        read_only: true,
1166                    })
1167                    .await?
1168            }
1169        };
1170
1171        let summary = vault.summary().clone();
1172        let buffer = self.write_vault(&vault, Internal).await?;
1173
1174        // Add the summary to the vaults we are managing
1175        self.add_summary(summary.clone(), Internal);
1176
1177        // Initialize the local cache for the event log
1178        self.create_folder_entry(vault, true, None, Internal)
1179            .await?;
1180
1181        self.unlock_folder(summary.id(), &key).await?;
1182
1183        Ok((buffer, key, summary))
1184    }
1185
1186    /// Create a new folder.
1187    async fn create_folder(
1188        &mut self,
1189        options: NewFolderOptions,
1190    ) -> Result<(Vec<u8>, AccessKey, Summary, AccountEvent)> {
1191        self.guard_authenticated(Internal)?;
1192
1193        let (buf, key, summary) =
1194            self.prepare_folder(options, Internal).await?;
1195
1196        let account_event =
1197            AccountEvent::CreateFolder(*summary.id(), buf.clone());
1198        let account_log = self.account_log().await?;
1199        let mut account_log = account_log.write().await;
1200        account_log.apply(&[account_event.clone()]).await?;
1201
1202        // Must save the folder access key
1203        self.authenticated_user_mut()
1204            .ok_or_else(|| AuthenticationError::NotAuthenticated)?
1205            .save_folder_password(summary.id(), key.clone())
1206            .await?;
1207
1208        #[cfg(feature = "audit")]
1209        {
1210            let audit_event: AuditEvent =
1211                (self.account_id(), &account_event).into();
1212            append_audit_events(&[audit_event]).await?;
1213        }
1214
1215        Ok((buf, key, summary, account_event))
1216    }
1217
1218    /// Delete a folder.
1219    async fn delete_folder(
1220        &mut self,
1221        folder_id: &VaultId,
1222        apply_event: bool,
1223    ) -> Result<Vec<Event>> {
1224        self.guard_authenticated(Internal)?;
1225
1226        // Remove the files
1227        self.remove_vault(folder_id, Internal).await?;
1228
1229        // Remove local state
1230        self.remove_folder_entry(folder_id, Internal)?;
1231
1232        let mut events = Vec::new();
1233
1234        #[cfg(feature = "files")]
1235        {
1236            let mut file_events = self
1237                .external_file_manager_mut()
1238                .ok_or_else(|| AuthenticationError::NotAuthenticated)?
1239                .delete_folder_files(folder_id)
1240                .await?;
1241
1242            let file_log = self.file_log().await?;
1243            let mut writer = file_log.write().await;
1244            writer.apply(file_events.as_slice()).await?;
1245            for event in file_events.drain(..) {
1246                events.push(Event::File(event));
1247            }
1248        }
1249
1250        // Clean the search index
1251        #[cfg(feature = "search")]
1252        if let Some(index) = self.search_index_mut() {
1253            index.remove_folder(folder_id).await;
1254        }
1255
1256        self.authenticated_user_mut()
1257            .ok_or_else(|| AuthenticationError::NotAuthenticated)?
1258            .remove_folder_password(folder_id)
1259            .await?;
1260
1261        let account_event = AccountEvent::DeleteFolder(*folder_id);
1262
1263        if apply_event {
1264            let account_log = self.account_log().await?;
1265            let mut account_log = account_log.write().await;
1266            account_log.apply(&[account_event.clone()]).await?;
1267        }
1268
1269        #[cfg(feature = "audit")]
1270        {
1271            let audit_event: AuditEvent =
1272                (self.account_id(), &account_event).into();
1273            append_audit_events(&[audit_event]).await?;
1274        }
1275
1276        events.insert(0, Event::Account(account_event));
1277
1278        Ok(events)
1279    }
1280
1281    /// Restore a folder from an event log.
1282    async fn restore_folder(
1283        &mut self,
1284        records: Vec<EventRecord>,
1285        key: &AccessKey,
1286    ) -> Result<Summary> {
1287        self.guard_authenticated(Internal)?;
1288
1289        let (mut folder, vault) =
1290            self.initialize_folder(records, Internal).await?;
1291
1292        // Unlock the folder
1293        folder.unlock(key).await?;
1294        self.folders_mut().insert(*vault.id(), folder);
1295
1296        let summary = vault.summary().to_owned();
1297        self.add_summary(summary.clone(), Internal);
1298
1299        #[cfg(feature = "search")]
1300        if let Some(index) = self.search_index_mut() {
1301            // Ensure the imported secrets are in the search index
1302            index.add_vault(vault, key).await?;
1303        }
1304
1305        Ok(summary)
1306    }
1307
1308    /// Create or update a vault.
1309    #[doc(hidden)]
1310    async fn upsert_vault_buffer(
1311        &mut self,
1312        buffer: impl AsRef<[u8]> + Send,
1313        key: Option<&AccessKey>,
1314        creation_time: Option<&UtcDateTime>,
1315        _: Internal,
1316    ) -> Result<(bool, WriteEvent, Summary)> {
1317        let vault: Vault = decode(buffer.as_ref()).await?;
1318        let exists = self.find(|s| s.id() == vault.id()).is_some();
1319        let summary = vault.summary().clone();
1320
1321        #[cfg(feature = "search")]
1322        if exists {
1323            if let Some(index) = self.search_index_mut() {
1324                // Clean entries from the search index
1325                index.remove_folder(summary.id()).await;
1326            }
1327        }
1328
1329        self.write_vault(&vault, Internal).await?;
1330
1331        if !exists {
1332            // Add the summary to the vaults we are managing
1333            self.add_summary(summary.clone(), Internal);
1334        } else {
1335            // Otherwise update with the new summary
1336            if let Some(existing) = self
1337                .summaries_mut(Internal)
1338                .iter_mut()
1339                .find(|s| s.id() == summary.id())
1340            {
1341                *existing = summary.clone();
1342            }
1343        }
1344
1345        #[cfg(feature = "search")]
1346        if let Some(key) = key {
1347            if let Some(index) = self.search_index_mut() {
1348                // Ensure the imported secrets are in the search index
1349                index.add_vault(vault.clone(), key).await?;
1350            }
1351        }
1352
1353        let event = vault.into_event().await?;
1354
1355        // Initialize the local cache for event log
1356        self.create_folder_entry(vault, true, creation_time, Internal)
1357            .await?;
1358
1359        // Must ensure the folder is unlocked
1360        if let Some(key) = key {
1361            self.unlock_folder(summary.id(), key).await?;
1362        }
1363
1364        Ok((exists, event, summary))
1365    }
1366
1367    /// Import a folder into an existing account.
1368    ///
1369    /// If a folder with the same identifier already exists
1370    /// it is overwritten.
1371    ///
1372    /// Buffer is the encoded representation of the vault.
1373    ///
1374    /// # Authentication
1375    ///
1376    /// Can be called when the account is not authenticated.
1377    async fn import_folder(
1378        &mut self,
1379        buffer: impl AsRef<[u8]> + Send,
1380        key: Option<&AccessKey>,
1381        apply_event: bool,
1382        creation_time: Option<&UtcDateTime>,
1383    ) -> Result<(Event, Summary)> {
1384        let (exists, write_event, summary) = self
1385            .upsert_vault_buffer(
1386                buffer.as_ref(),
1387                key,
1388                creation_time,
1389                Internal,
1390            )
1391            .await?;
1392
1393        // If there is an existing folder
1394        // and we are overwriting then log the update
1395        // folder event
1396        let account_event = if exists {
1397            AccountEvent::UpdateFolder(
1398                *summary.id(),
1399                buffer.as_ref().to_owned(),
1400            )
1401        // Otherwise a create event
1402        } else {
1403            AccountEvent::CreateFolder(
1404                *summary.id(),
1405                buffer.as_ref().to_owned(),
1406            )
1407        };
1408
1409        if apply_event {
1410            let account_log = self.account_log().await?;
1411            let mut account_log = account_log.write().await;
1412            account_log.apply(&[account_event.clone()]).await?;
1413        }
1414
1415        #[cfg(feature = "audit")]
1416        {
1417            let audit_event: AuditEvent =
1418                (self.account_id(), &account_event).into();
1419            append_audit_events(&[audit_event]).await?;
1420        }
1421
1422        Ok((Event::Folder(account_event, write_event), summary))
1423    }
1424
1425    /// Get the history of events for a vault.
1426    async fn history(
1427        &self,
1428        folder_id: &VaultId,
1429    ) -> Result<Vec<(CommitHash, UtcDateTime, WriteEvent)>> {
1430        self.guard_authenticated(Internal)?;
1431
1432        let folder = self
1433            .folders()
1434            .get(folder_id)
1435            .ok_or(StorageError::FolderNotFound(*folder_id))?;
1436        let event_log = folder.event_log();
1437        let log_file = event_log.read().await;
1438        let mut records = Vec::new();
1439
1440        let stream = log_file.event_stream(false).await;
1441        pin_mut!(stream);
1442
1443        while let Some(result) = stream.next().await {
1444            let (record, event) = result?;
1445            let commit = *record.commit();
1446            let time = record.time().clone();
1447            records.push((commit, time, event));
1448        }
1449
1450        Ok(records)
1451    }
1452
1453    /// Commit state of the identity folder.
1454    ///
1455    /// # Authentication
1456    ///
1457    /// Can be called when the account is not authenticated.
1458    async fn identity_state(&self) -> Result<CommitState> {
1459        let identity_log = self.identity_log().await?;
1460        let reader = identity_log.read().await;
1461        Ok(reader.tree().commit_state()?)
1462    }
1463
1464    /// Get the commit state for a folder.
1465    ///
1466    /// The folder must have at least one commit.
1467    ///
1468    /// # Authentication
1469    ///
1470    /// Can be called when the account is not authenticated.
1471    async fn commit_state(&self, folder_id: &VaultId) -> Result<CommitState> {
1472        let folder = self
1473            .folders()
1474            .get(folder_id)
1475            .ok_or_else(|| StorageError::FolderNotFound(*folder_id))?;
1476        let event_log = folder.event_log();
1477        let log_file = event_log.read().await;
1478        Ok(log_file.tree().commit_state()?)
1479    }
1480
1481    /// External file manager.
1482    #[cfg(feature = "files")]
1483    fn external_file_manager(&self) -> Option<&ExternalFileManager>;
1484
1485    /// Mutable external file manager.
1486    #[cfg(feature = "files")]
1487    fn external_file_manager_mut(
1488        &mut self,
1489    ) -> Option<&mut ExternalFileManager>;
1490
1491    /// Set the external file manager.
1492    #[cfg(feature = "files")]
1493    #[doc(hidden)]
1494    fn set_external_file_manager(
1495        &mut self,
1496        file_manager: Option<ExternalFileManager>,
1497        _: Internal,
1498    );
1499
1500    /// Search index reference.
1501    #[cfg(feature = "search")]
1502    fn search_index(&self) -> Option<&AccountSearch>;
1503
1504    /// Mutable search index reference.
1505    #[cfg(feature = "search")]
1506    fn search_index_mut(&mut self) -> Option<&mut AccountSearch>;
1507
1508    /// Set the search index.
1509    #[cfg(feature = "search")]
1510    #[doc(hidden)]
1511    fn set_search_index(&mut self, user: Option<AccountSearch>, _: Internal);
1512
1513    /// Initialize the search index.
1514    ///
1515    /// This should be called after a user has signed in to
1516    /// create the initial search index.
1517    #[cfg(feature = "search")]
1518    async fn initialize_search_index(
1519        &mut self,
1520        keys: &FolderKeys,
1521    ) -> Result<(DocumentCount, Vec<Summary>)> {
1522        self.guard_authenticated(Internal)?;
1523
1524        // Find the id of an archive folder
1525        let summaries = {
1526            let summaries = self.list_folders();
1527            let mut archive: Option<VaultId> = None;
1528            for summary in summaries {
1529                if summary.flags().is_archive() {
1530                    archive = Some(*summary.id());
1531                    break;
1532                }
1533            }
1534            if let Some(index) = self.search_index() {
1535                let mut writer = index.search_index.write().await;
1536                writer.set_archive_id(archive);
1537            }
1538            summaries
1539        };
1540        let folders = summaries.to_vec();
1541        Ok((self.build_search_index(keys).await?, folders))
1542    }
1543
1544    /// Build the search index for all folders.
1545    #[cfg(feature = "search")]
1546    async fn build_search_index(
1547        &mut self,
1548        keys: &FolderKeys,
1549    ) -> Result<DocumentCount> {
1550        self.guard_authenticated(Internal)?;
1551
1552        use sos_core::AuthenticationError;
1553
1554        {
1555            let index = self
1556                .search_index()
1557                .ok_or_else(|| AuthenticationError::NotAuthenticated)?;
1558            let search_index = index.search();
1559            let mut writer = search_index.write().await;
1560
1561            // Clear search index first
1562            writer.remove_all();
1563
1564            for (folder_id, key) in &keys.0 {
1565                if let Some(folder) = self.folders_mut().get_mut(folder_id) {
1566                    let access_point = folder.access_point();
1567                    let mut access_point = access_point.lock().await;
1568                    access_point.unlock(key).await?;
1569                    writer.add_folder(&*access_point).await?;
1570                }
1571            }
1572        }
1573
1574        let count = if let Some(index) = self.search_index() {
1575            index.document_count().await
1576        } else {
1577            Default::default()
1578        };
1579
1580        Ok(count)
1581    }
1582}