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(folder_id, &mut *log_file).await?;
574        }
575
576        // Refresh in-memory vault and mirrored copy
577        let buffer = self.refresh_vault(folder_id, key, Internal).await?;
578
579        let account_event = AccountEvent::CompactFolder(*folder_id, buffer);
580
581        let account_log = self.account_log().await?;
582        let mut account_log = account_log.write().await;
583        account_log.apply(&[account_event.clone()]).await?;
584
585        Ok(account_event)
586    }
587
588    /// Set the name of a folder.
589    async fn rename_folder(
590        &mut self,
591        folder_id: &VaultId,
592        name: impl AsRef<str> + Send,
593    ) -> Result<Event> {
594        self.guard_authenticated(Internal)?;
595
596        // Update the in-memory name.
597        self.set_folder_name(folder_id, name.as_ref(), Internal)?;
598
599        let folder = self
600            .folders_mut()
601            .get_mut(folder_id)
602            .ok_or(StorageError::FolderNotFound(*folder_id))?;
603
604        folder.rename_folder(name.as_ref()).await?;
605
606        let account_event =
607            AccountEvent::RenameFolder(*folder_id, name.as_ref().to_owned());
608
609        let account_log = self.account_log().await?;
610        let mut account_log = account_log.write().await;
611        account_log.apply(&[account_event.clone()]).await?;
612
613        #[cfg(feature = "audit")]
614        {
615            let audit_event: AuditEvent =
616                (self.account_id(), &account_event).into();
617            append_audit_events(&[audit_event]).await?;
618        }
619
620        Ok(Event::Account(account_event))
621    }
622
623    /// Update the flags for a folder.
624    async fn update_folder_flags(
625        &mut self,
626        folder_id: &VaultId,
627        flags: VaultFlags,
628    ) -> Result<Event> {
629        self.guard_authenticated(Internal)?;
630
631        // Update the in-memory name.
632        self.set_folder_flags(folder_id, flags.clone(), Internal)?;
633
634        let folder = self
635            .folders_mut()
636            .get_mut(folder_id)
637            .ok_or(StorageError::FolderNotFound(*folder_id))?;
638
639        let event = folder.update_folder_flags(flags).await?;
640        let event = Event::Write(*folder_id, event);
641
642        #[cfg(feature = "audit")]
643        {
644            let audit_event: AuditEvent = (self.account_id(), &event).into();
645            append_audit_events(&[audit_event]).await?;
646        }
647
648        Ok(event)
649    }
650
651    /// Update the in-memory name for a folder.
652    #[doc(hidden)]
653    fn set_folder_name(
654        &mut self,
655        folder_id: &VaultId,
656        name: impl AsRef<str>,
657        _: Internal,
658    ) -> Result<()> {
659        if let Some(summary) = self
660            .summaries_mut(Internal)
661            .iter_mut()
662            .find(|f| f.id() == folder_id)
663        {
664            summary.set_name(name.as_ref().to_owned());
665        }
666        Ok(())
667    }
668
669    /// Update the in-memory name for a folder.
670    #[doc(hidden)]
671    fn set_folder_flags(
672        &mut self,
673        folder_id: &VaultId,
674        flags: VaultFlags,
675        _: Internal,
676    ) -> Result<()> {
677        if let Some(summary) = self
678            .summaries_mut(Internal)
679            .iter_mut()
680            .find(|f| f.id() == folder_id)
681        {
682            *summary.flags_mut() = flags;
683        }
684        Ok(())
685    }
686
687    /// Get the description for a folder.
688    async fn description(&self, folder_id: &VaultId) -> Result<String> {
689        self.guard_authenticated(Internal)?;
690
691        let folder = self
692            .folders()
693            .get(folder_id)
694            .ok_or_else(|| StorageError::FolderNotFound(*folder_id))?;
695        Ok(folder.description().await?)
696    }
697
698    /// Set the description of the currently open folder.
699    async fn set_description(
700        &mut self,
701        folder_id: &VaultId,
702        description: impl AsRef<str> + Send,
703    ) -> Result<WriteEvent> {
704        self.guard_authenticated(Internal)?;
705
706        let folder = self
707            .folders_mut()
708            .get_mut(folder_id)
709            .ok_or_else(|| StorageError::FolderNotFound(*folder_id))?;
710        Ok(folder.set_description(description).await?)
711    }
712}
713
714/// Secret management for client storage.
715#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
716#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
717pub trait ClientSecretStorage {
718    /// Create a secret in the currently open vault.
719    async fn create_secret(
720        &mut self,
721        secret_data: SecretRow,
722        #[allow(unused_mut, unused_variables)] mut options: AccessOptions,
723    ) -> Result<StorageChangeEvent>;
724
725    /// Read the encrypted contents of a secret.
726    async fn raw_secret(
727        &self,
728        folder_id: &VaultId,
729        secret_id: &SecretId,
730    ) -> Result<Option<(VaultCommit, ReadEvent)>>;
731
732    /// Read a secret in the currently open folder.
733    async fn read_secret(
734        &self,
735        id: &SecretId,
736        options: &AccessOptions,
737    ) -> Result<(Summary, SecretMeta, Secret, ReadEvent)>;
738
739    /// Update a secret in the currently open folder.
740    async fn update_secret(
741        &mut self,
742        secret_id: &SecretId,
743        meta: SecretMeta,
744        secret: Option<Secret>,
745        #[allow(unused_mut, unused_variables)] mut options: AccessOptions,
746    ) -> Result<StorageChangeEvent>;
747
748    /// Write a secret.
749    ///
750    /// Unlike `update_secret()` this function does not support moving
751    /// between folders or managing external files which allows us
752    /// to avoid recursion when handling embedded file secrets which
753    /// require rewriting the secret once the files have been encrypted.
754    #[doc(hidden)]
755    async fn write_secret(
756        &mut self,
757        folder: &Summary,
758        id: &SecretId,
759        mut secret_data: SecretRow,
760        #[allow(unused_variables)] is_update: bool,
761        _: Internal,
762    ) -> Result<WriteEvent>;
763
764    /// Delete a secret in the currently open vault.
765    async fn delete_secret(
766        &mut self,
767        secret_id: &SecretId,
768        #[allow(unused_mut, unused_variables)] mut options: AccessOptions,
769    ) -> Result<StorageChangeEvent>;
770
771    /// Remove a secret.
772    ///
773    /// Any external files for the secret are left intact.
774    async fn remove_secret(
775        &mut self,
776        id: &SecretId,
777        options: &AccessOptions,
778    ) -> Result<WriteEvent>;
779}
780
781/// Internal utility functions for event log management.
782#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
783#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
784#[doc(hidden)]
785pub trait ClientEventLogStorage {
786    /// Initialize the device event log.
787    #[doc(hidden)]
788    async fn initialize_device_log(
789        &self,
790        device: TrustedDevice,
791        _: Internal,
792    ) -> Result<(DeviceEventLog, IndexSet<TrustedDevice>)>;
793
794    /// Initialize the file event log.
795    #[cfg(feature = "files")]
796    #[doc(hidden)]
797    async fn initialize_file_log(&self, _: Internal) -> Result<FileEventLog>;
798
799    /// Set the identity event log.
800    #[doc(hidden)]
801    fn set_identity_log(
802        &mut self,
803        log: Arc<RwLock<FolderEventLog>>,
804        _: Internal,
805    );
806
807    /// Set the device event log.
808    #[doc(hidden)]
809    fn set_device_log(
810        &mut self,
811        log: Arc<RwLock<DeviceEventLog>>,
812        _: Internal,
813    );
814
815    /// Set the file event log.
816    #[cfg(feature = "files")]
817    #[doc(hidden)]
818    fn set_file_log(&mut self, log: Arc<RwLock<FileEventLog>>, _: Internal);
819}
820
821/// Account management for client storage.
822#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
823#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
824pub trait ClientAccountStorage:
825    StorageEventLogs<Error = Error>
826    + ClientBaseStorage
827    + ClientDeviceStorage
828    + ClientFolderStorage
829    + ClientSecretStorage
830    + ClientEventLogStorage
831{
832    /// Rename the account.
833    async fn rename_account(&mut self, account_name: String) -> Result<()> {
834        let authenticated_user = self
835            .authenticated_user_mut()
836            .ok_or(AuthenticationError::NotAuthenticated)?;
837        authenticated_user.rename_account(account_name).await?;
838        Ok(())
839    }
840
841    /// Import an account from a change set of event logs.
842    ///
843    /// Intended to be used during pairing to create a new
844    /// account from a collection of patches.
845    ///
846    /// # Authentication
847    ///
848    /// Can be called when the storage is not authenticated.
849    async fn import_account(
850        &mut self,
851        account_data: &CreateSet,
852    ) -> Result<()>;
853
854    /// List the secret ids for a folder.
855    async fn list_secret_ids(
856        &self,
857        folder_id: &VaultId,
858    ) -> Result<Vec<SecretId>>;
859
860    /// Create a device vault from a buffer.
861    ///
862    /// Used during pairing enrollment to initialize
863    /// a device vault received from the authorizing device.
864    ///
865    /// # Authentication
866    ///
867    /// Can be called when the storage is not authenticated.
868    async fn create_device_vault(
869        &mut self,
870        device_vault: &[u8],
871    ) -> Result<()>;
872
873    /// Delete the account for this user.
874    async fn delete_account(&self) -> Result<Event>;
875
876    /// Set the storage as authenticated.
877    async fn authenticate(
878        &mut self,
879        authenticated_user: Identity,
880    ) -> Result<()> {
881        // Note that attempting to access the identity of an authenticated
882        // user is an error when not authenticated.
883        let identity_log = authenticated_user.identity()?.event_log();
884        let device = authenticated_user
885            .identity()?
886            .devices()?
887            .current_device(None);
888        self.set_identity_log(identity_log, Internal);
889
890        let (device_log, devices) =
891            self.initialize_device_log(device, Internal).await?;
892        self.set_device_log(Arc::new(RwLock::new(device_log)), Internal);
893        self.set_devices(devices, Internal);
894
895        #[cfg(feature = "files")]
896        {
897            let file_log = self.initialize_file_log(Internal).await?;
898            let file_log = Arc::new(RwLock::new(file_log));
899            let file_password =
900                authenticated_user.find_file_encryption_password().await?;
901
902            self.set_external_file_manager(
903                Some(ExternalFileManager::new(
904                    self.paths().clone(),
905                    file_log.clone(),
906                    file_password,
907                )),
908                Internal,
909            );
910            self.set_file_log(file_log, Internal);
911        }
912
913        #[cfg(feature = "search")]
914        {
915            self.set_search_index(Some(AccountSearch::new()), Internal);
916        }
917        self.set_authenticated_user(Some(authenticated_user), Internal);
918
919        Ok(())
920    }
921
922    /// Change the password for a folder.
923    ///
924    /// If the target folder is the currently selected folder
925    /// the currently selected vault is unlocked with the new
926    /// passphrase on success.
927    async fn change_password(
928        &mut self,
929        vault: &Vault,
930        current_key: AccessKey,
931        new_key: AccessKey,
932    ) -> Result<()> {
933        self.guard_authenticated(Internal)?;
934
935        let folder_id = vault.id();
936        let (new_key, new_vault, event_log_events) =
937            ChangePassword::new(vault, current_key, new_key, None)
938                .build()
939                .await?;
940
941        let buffer = self.update_vault(&new_vault, event_log_events).await?;
942
943        let account_event =
944            AccountEvent::ChangeFolderPassword(*folder_id, buffer);
945
946        // Refresh the in-memory and disc-based mirror
947        self.refresh_vault(vault.id(), &new_key, Internal).await?;
948
949        if let Some(folder) = self.folders_mut().get_mut(vault.id()) {
950            let access_point = folder.access_point();
951            let mut access_point = access_point.lock().await;
952            access_point.unlock(&new_key).await?;
953        }
954
955        // Save the new password
956        self.authenticated_user_mut()
957            .ok_or_else(|| AuthenticationError::NotAuthenticated)?
958            .save_folder_password(folder_id, new_key)
959            .await?;
960
961        let account_log = self.account_log().await?;
962        let mut account_log = account_log.write().await;
963        account_log.apply(&[account_event]).await?;
964
965        Ok(())
966    }
967
968    /// Sign out the authenticated user.
969    async fn sign_out(&mut self) -> Result<()> {
970        if let Some(authenticated) = self.authenticated_user_mut() {
971            tracing::debug!("client_storage::sign_out_identity");
972            // Forget private identity information
973            authenticated.sign_out().await?;
974        }
975
976        #[cfg(feature = "search")]
977        {
978            tracing::debug!("client_storage::drop_search_index");
979            self.set_search_index(None, Internal);
980        }
981
982        #[cfg(feature = "files")]
983        {
984            tracing::debug!("client_storage::drop_external_file_manager");
985            self.set_external_file_manager(None, Internal);
986        }
987
988        tracing::debug!("client_storage::drop_authenticated_user");
989        self.set_authenticated_user(None, Internal);
990
991        Ok(())
992    }
993
994    /// Import a login vault and generate the event but
995    /// do not write the event to the account event log.
996    ///
997    /// This is used when merging account event logs to ensure
998    /// the `AccountEvent::UpdateIdentity` event is not duplicated.
999    ///
1000    /// Typically the handlers that update storage but don't append log
1001    /// events are declared in the storage implementation but the
1002    /// identity log is managed by the account so this must exist here.
1003    #[doc(hidden)]
1004    async fn import_login_vault(
1005        &mut self,
1006        vault: Vault,
1007    ) -> Result<AccountEvent> {
1008        self.guard_authenticated(Internal)?;
1009
1010        let user = self
1011            .authenticated_user()
1012            .ok_or(AuthenticationError::NotAuthenticated)?;
1013
1014        vault
1015            .summary()
1016            .flags()
1017            .contains(VaultFlags::IDENTITY)
1018            .then_some(())
1019            .ok_or_else(|| sos_login::Error::NotIdentityFolder)?;
1020
1021        // Update the identity vault
1022        let buffer = self.write_login_vault(&vault, Internal).await?;
1023
1024        // Update the events for the identity vault
1025        let identity = user.identity()?;
1026        let event_log = identity.event_log();
1027        let mut event_log = event_log.write().await;
1028        event_log.clear().await?;
1029
1030        let (_, events) = FolderReducer::split::<Error>(vault).await?;
1031        event_log.apply(events.as_slice()).await?;
1032
1033        Ok(AccountEvent::UpdateIdentity(buffer))
1034    }
1035
1036    /// Unlock all folders.
1037    async fn unlock(&mut self, keys: &FolderKeys) -> Result<()> {
1038        self.guard_authenticated(Internal)?;
1039
1040        for (id, folder) in self.folders_mut().iter_mut() {
1041            if let Some(key) = keys.find(id) {
1042                folder.unlock(key).await?;
1043            } else {
1044                tracing::error!(
1045                    folder_id = %id,
1046                    "unlock::no_folder_key",
1047                );
1048            }
1049        }
1050        Ok(())
1051    }
1052
1053    /// Lock all folders.
1054    ///
1055    /// # Authentication
1056    ///
1057    /// Can be called when the storage is not authenticated.
1058    async fn lock(&mut self) {
1059        for (_, folder) in self.folders_mut().iter_mut() {
1060            folder.lock().await;
1061        }
1062    }
1063
1064    /// Unlock a folder.
1065    async fn unlock_folder(
1066        &mut self,
1067        folder_id: &VaultId,
1068        key: &AccessKey,
1069    ) -> Result<()> {
1070        self.guard_authenticated(Internal)?;
1071
1072        let folder = self
1073            .folders_mut()
1074            .get_mut(folder_id)
1075            .ok_or(StorageError::FolderNotFound(*folder_id))?;
1076        folder.unlock(key).await?;
1077        Ok(())
1078    }
1079
1080    /// Lock a folder.
1081    ///
1082    /// # Authentication
1083    ///
1084    /// Can be called when the storage is not authenticated.
1085    async fn lock_folder(&mut self, id: &VaultId) -> Result<()> {
1086        let folder = self
1087            .folders_mut()
1088            .get_mut(id)
1089            .ok_or(StorageError::FolderNotFound(*id))?;
1090        folder.lock().await;
1091        Ok(())
1092    }
1093
1094    /// Create the data for a new account.
1095    ///
1096    /// # Authentication
1097    ///
1098    /// Can be called when the storage is not authenticated.
1099    async fn create_account(
1100        &mut self,
1101        account: &AccountPack,
1102    ) -> Result<Vec<Event>> {
1103        let mut events = Vec::new();
1104
1105        let create_account = Event::CreateAccount(account.account_id.into());
1106
1107        // Each folder import will create an audit event
1108        // but we want the create account to be in the audit
1109        // log before the folder create audit events
1110        #[cfg(feature = "audit")]
1111        {
1112            let audit_event: AuditEvent =
1113                (self.account_id(), &create_account).into();
1114            append_audit_events(&[audit_event]).await?;
1115        }
1116
1117        // Import folders
1118        for folder in &account.folders {
1119            let buffer = encode(folder).await?;
1120            let (event, _) =
1121                self.import_folder(buffer, None, true, None).await?;
1122            events.push(event);
1123        }
1124
1125        events.insert(0, create_account);
1126
1127        Ok(events)
1128    }
1129
1130    /// Prepare a new folder.
1131    #[doc(hidden)]
1132    async fn prepare_folder(
1133        &mut self,
1134        mut options: NewFolderOptions,
1135        _: Internal,
1136    ) -> Result<(Vec<u8>, AccessKey, Summary)> {
1137        let key = if let Some(key) = options.key.take() {
1138            key
1139        } else {
1140            let (passphrase, _) = generate_passphrase()?;
1141            AccessKey::Password(passphrase)
1142        };
1143
1144        let builder = VaultBuilder::new()
1145            .flags(options.flags.unwrap_or_default())
1146            .cipher(options.cipher.unwrap_or_default())
1147            .kdf(options.kdf.unwrap_or_default())
1148            .public_name(options.name);
1149
1150        let vault = match &key {
1151            AccessKey::Password(password) => {
1152                builder
1153                    .build(BuilderCredentials::Password(
1154                        password.clone(),
1155                        None,
1156                    ))
1157                    .await?
1158            }
1159            AccessKey::Identity(id) => {
1160                builder
1161                    .build(BuilderCredentials::Shared {
1162                        owner: id,
1163                        recipients: vec![],
1164                        read_only: true,
1165                    })
1166                    .await?
1167            }
1168        };
1169
1170        let summary = vault.summary().clone();
1171        let buffer = self.write_vault(&vault, Internal).await?;
1172
1173        // Add the summary to the vaults we are managing
1174        self.add_summary(summary.clone(), Internal);
1175
1176        // Initialize the local cache for the event log
1177        self.create_folder_entry(vault, true, None, Internal)
1178            .await?;
1179
1180        self.unlock_folder(summary.id(), &key).await?;
1181
1182        Ok((buffer, key, summary))
1183    }
1184
1185    /// Create a new folder.
1186    async fn create_folder(
1187        &mut self,
1188        options: NewFolderOptions,
1189    ) -> Result<(Vec<u8>, AccessKey, Summary, AccountEvent)> {
1190        self.guard_authenticated(Internal)?;
1191
1192        let (buf, key, summary) =
1193            self.prepare_folder(options, Internal).await?;
1194
1195        let account_event =
1196            AccountEvent::CreateFolder(*summary.id(), buf.clone());
1197        let account_log = self.account_log().await?;
1198        let mut account_log = account_log.write().await;
1199        account_log.apply(&[account_event.clone()]).await?;
1200
1201        // Must save the folder access key
1202        self.authenticated_user_mut()
1203            .ok_or_else(|| AuthenticationError::NotAuthenticated)?
1204            .save_folder_password(summary.id(), key.clone())
1205            .await?;
1206
1207        #[cfg(feature = "audit")]
1208        {
1209            let audit_event: AuditEvent =
1210                (self.account_id(), &account_event).into();
1211            append_audit_events(&[audit_event]).await?;
1212        }
1213
1214        Ok((buf, key, summary, account_event))
1215    }
1216
1217    /// Delete a folder.
1218    async fn delete_folder(
1219        &mut self,
1220        folder_id: &VaultId,
1221        apply_event: bool,
1222    ) -> Result<Vec<Event>> {
1223        self.guard_authenticated(Internal)?;
1224
1225        // Remove the files
1226        self.remove_vault(folder_id, Internal).await?;
1227
1228        // Remove local state
1229        self.remove_folder_entry(folder_id, Internal)?;
1230
1231        let mut events = Vec::new();
1232
1233        #[cfg(feature = "files")]
1234        {
1235            let mut file_events = self
1236                .external_file_manager_mut()
1237                .ok_or_else(|| AuthenticationError::NotAuthenticated)?
1238                .delete_folder_files(folder_id)
1239                .await?;
1240
1241            let file_log = self.file_log().await?;
1242            let mut writer = file_log.write().await;
1243            writer.apply(file_events.as_slice()).await?;
1244            for event in file_events.drain(..) {
1245                events.push(Event::File(event));
1246            }
1247        }
1248
1249        // Clean the search index
1250        #[cfg(feature = "search")]
1251        if let Some(index) = self.search_index_mut() {
1252            index.remove_folder(folder_id).await;
1253        }
1254
1255        self.authenticated_user_mut()
1256            .ok_or_else(|| AuthenticationError::NotAuthenticated)?
1257            .remove_folder_password(folder_id)
1258            .await?;
1259
1260        let account_event = AccountEvent::DeleteFolder(*folder_id);
1261
1262        if apply_event {
1263            let account_log = self.account_log().await?;
1264            let mut account_log = account_log.write().await;
1265            account_log.apply(&[account_event.clone()]).await?;
1266        }
1267
1268        #[cfg(feature = "audit")]
1269        {
1270            let audit_event: AuditEvent =
1271                (self.account_id(), &account_event).into();
1272            append_audit_events(&[audit_event]).await?;
1273        }
1274
1275        events.insert(0, Event::Account(account_event));
1276
1277        Ok(events)
1278    }
1279
1280    /// Restore a folder from an event log.
1281    async fn restore_folder(
1282        &mut self,
1283        records: Vec<EventRecord>,
1284        key: &AccessKey,
1285    ) -> Result<Summary> {
1286        self.guard_authenticated(Internal)?;
1287
1288        let (mut folder, vault) =
1289            self.initialize_folder(records, Internal).await?;
1290
1291        // Unlock the folder
1292        folder.unlock(key).await?;
1293        self.folders_mut().insert(*vault.id(), folder);
1294
1295        let summary = vault.summary().to_owned();
1296        self.add_summary(summary.clone(), Internal);
1297
1298        #[cfg(feature = "search")]
1299        if let Some(index) = self.search_index_mut() {
1300            // Ensure the imported secrets are in the search index
1301            index.add_vault(vault, key).await?;
1302        }
1303
1304        Ok(summary)
1305    }
1306
1307    /// Create or update a vault.
1308    #[doc(hidden)]
1309    async fn upsert_vault_buffer(
1310        &mut self,
1311        buffer: impl AsRef<[u8]> + Send,
1312        key: Option<&AccessKey>,
1313        creation_time: Option<&UtcDateTime>,
1314        _: Internal,
1315    ) -> Result<(bool, WriteEvent, Summary)> {
1316        let vault: Vault = decode(buffer.as_ref()).await?;
1317        let exists = self.find(|s| s.id() == vault.id()).is_some();
1318        let summary = vault.summary().clone();
1319
1320        #[cfg(feature = "search")]
1321        if exists {
1322            if let Some(index) = self.search_index_mut() {
1323                // Clean entries from the search index
1324                index.remove_folder(summary.id()).await;
1325            }
1326        }
1327
1328        self.write_vault(&vault, Internal).await?;
1329
1330        if !exists {
1331            // Add the summary to the vaults we are managing
1332            self.add_summary(summary.clone(), Internal);
1333        } else {
1334            // Otherwise update with the new summary
1335            if let Some(existing) = self
1336                .summaries_mut(Internal)
1337                .iter_mut()
1338                .find(|s| s.id() == summary.id())
1339            {
1340                *existing = summary.clone();
1341            }
1342        }
1343
1344        #[cfg(feature = "search")]
1345        if let Some(key) = key {
1346            if let Some(index) = self.search_index_mut() {
1347                // Ensure the imported secrets are in the search index
1348                index.add_vault(vault.clone(), key).await?;
1349            }
1350        }
1351
1352        let event = vault.into_event().await?;
1353
1354        // Initialize the local cache for event log
1355        self.create_folder_entry(vault, true, creation_time, Internal)
1356            .await?;
1357
1358        // Must ensure the folder is unlocked
1359        if let Some(key) = key {
1360            self.unlock_folder(summary.id(), key).await?;
1361        }
1362
1363        Ok((exists, event, summary))
1364    }
1365
1366    /// Import a folder into an existing account.
1367    ///
1368    /// If a folder with the same identifier already exists
1369    /// it is overwritten.
1370    ///
1371    /// Buffer is the encoded representation of the vault.
1372    ///
1373    /// # Authentication
1374    ///
1375    /// Can be called when the account is not authenticated.
1376    async fn import_folder(
1377        &mut self,
1378        buffer: impl AsRef<[u8]> + Send,
1379        key: Option<&AccessKey>,
1380        apply_event: bool,
1381        creation_time: Option<&UtcDateTime>,
1382    ) -> Result<(Event, Summary)> {
1383        let (exists, write_event, summary) = self
1384            .upsert_vault_buffer(
1385                buffer.as_ref(),
1386                key,
1387                creation_time,
1388                Internal,
1389            )
1390            .await?;
1391
1392        // If there is an existing folder
1393        // and we are overwriting then log the update
1394        // folder event
1395        let account_event = if exists {
1396            AccountEvent::UpdateFolder(
1397                *summary.id(),
1398                buffer.as_ref().to_owned(),
1399            )
1400        // Otherwise a create event
1401        } else {
1402            AccountEvent::CreateFolder(
1403                *summary.id(),
1404                buffer.as_ref().to_owned(),
1405            )
1406        };
1407
1408        if apply_event {
1409            let account_log = self.account_log().await?;
1410            let mut account_log = account_log.write().await;
1411            account_log.apply(&[account_event.clone()]).await?;
1412        }
1413
1414        #[cfg(feature = "audit")]
1415        {
1416            let audit_event: AuditEvent =
1417                (self.account_id(), &account_event).into();
1418            append_audit_events(&[audit_event]).await?;
1419        }
1420
1421        Ok((Event::Folder(account_event, write_event), summary))
1422    }
1423
1424    /// Get the history of events for a vault.
1425    async fn history(
1426        &self,
1427        folder_id: &VaultId,
1428    ) -> Result<Vec<(CommitHash, UtcDateTime, WriteEvent)>> {
1429        self.guard_authenticated(Internal)?;
1430
1431        let folder = self
1432            .folders()
1433            .get(folder_id)
1434            .ok_or(StorageError::FolderNotFound(*folder_id))?;
1435        let event_log = folder.event_log();
1436        let log_file = event_log.read().await;
1437        let mut records = Vec::new();
1438
1439        let stream = log_file.event_stream(false).await;
1440        pin_mut!(stream);
1441
1442        while let Some(result) = stream.next().await {
1443            let (record, event) = result?;
1444            let commit = *record.commit();
1445            let time = record.time().clone();
1446            records.push((commit, time, event));
1447        }
1448
1449        Ok(records)
1450    }
1451
1452    /// Commit state of the identity folder.
1453    ///
1454    /// # Authentication
1455    ///
1456    /// Can be called when the account is not authenticated.
1457    async fn identity_state(&self) -> Result<CommitState> {
1458        let identity_log = self.identity_log().await?;
1459        let reader = identity_log.read().await;
1460        Ok(reader.tree().commit_state()?)
1461    }
1462
1463    /// Get the commit state for a folder.
1464    ///
1465    /// The folder must have at least one commit.
1466    ///
1467    /// # Authentication
1468    ///
1469    /// Can be called when the account is not authenticated.
1470    async fn commit_state(&self, folder_id: &VaultId) -> Result<CommitState> {
1471        let folder = self
1472            .folders()
1473            .get(folder_id)
1474            .ok_or_else(|| StorageError::FolderNotFound(*folder_id))?;
1475        let event_log = folder.event_log();
1476        let log_file = event_log.read().await;
1477        Ok(log_file.tree().commit_state()?)
1478    }
1479
1480    /// External file manager.
1481    #[cfg(feature = "files")]
1482    fn external_file_manager(&self) -> Option<&ExternalFileManager>;
1483
1484    /// Mutable external file manager.
1485    #[cfg(feature = "files")]
1486    fn external_file_manager_mut(
1487        &mut self,
1488    ) -> Option<&mut ExternalFileManager>;
1489
1490    /// Set the external file manager.
1491    #[cfg(feature = "files")]
1492    #[doc(hidden)]
1493    fn set_external_file_manager(
1494        &mut self,
1495        file_manager: Option<ExternalFileManager>,
1496        _: Internal,
1497    );
1498
1499    /// Search index reference.
1500    #[cfg(feature = "search")]
1501    fn search_index(&self) -> Option<&AccountSearch>;
1502
1503    /// Mutable search index reference.
1504    #[cfg(feature = "search")]
1505    fn search_index_mut(&mut self) -> Option<&mut AccountSearch>;
1506
1507    /// Set the search index.
1508    #[cfg(feature = "search")]
1509    #[doc(hidden)]
1510    fn set_search_index(&mut self, user: Option<AccountSearch>, _: Internal);
1511
1512    /// Initialize the search index.
1513    ///
1514    /// This should be called after a user has signed in to
1515    /// create the initial search index.
1516    #[cfg(feature = "search")]
1517    async fn initialize_search_index(
1518        &mut self,
1519        keys: &FolderKeys,
1520    ) -> Result<(DocumentCount, Vec<Summary>)> {
1521        self.guard_authenticated(Internal)?;
1522
1523        // Find the id of an archive folder
1524        let summaries = {
1525            let summaries = self.list_folders();
1526            let mut archive: Option<VaultId> = None;
1527            for summary in summaries {
1528                if summary.flags().is_archive() {
1529                    archive = Some(*summary.id());
1530                    break;
1531                }
1532            }
1533            if let Some(index) = self.search_index() {
1534                let mut writer = index.search_index.write().await;
1535                writer.set_archive_id(archive);
1536            }
1537            summaries
1538        };
1539        let folders = summaries.to_vec();
1540        Ok((self.build_search_index(keys).await?, folders))
1541    }
1542
1543    /// Build the search index for all folders.
1544    #[cfg(feature = "search")]
1545    async fn build_search_index(
1546        &mut self,
1547        keys: &FolderKeys,
1548    ) -> Result<DocumentCount> {
1549        self.guard_authenticated(Internal)?;
1550
1551        use sos_core::AuthenticationError;
1552
1553        {
1554            let index = self
1555                .search_index()
1556                .ok_or_else(|| AuthenticationError::NotAuthenticated)?;
1557            let search_index = index.search();
1558            let mut writer = search_index.write().await;
1559
1560            // Clear search index first
1561            writer.remove_all();
1562
1563            for (folder_id, key) in &keys.0 {
1564                if let Some(folder) = self.folders_mut().get_mut(folder_id) {
1565                    let access_point = folder.access_point();
1566                    let mut access_point = access_point.lock().await;
1567                    access_point.unlock(key).await?;
1568                    writer.add_folder(&*access_point).await?;
1569                }
1570            }
1571        }
1572
1573        let count = if let Some(index) = self.search_index() {
1574            index.document_count().await
1575        } else {
1576            Default::default()
1577        };
1578
1579        Ok(count)
1580    }
1581}