sos_client_storage/
storage.rs

1use crate::{
2    database::ClientDatabaseStorage,
3    filesystem::ClientFileSystemStorage,
4    traits::{
5        private::Internal, ClientAccountStorage, ClientBaseStorage,
6        ClientDeviceStorage, ClientFolderStorage, ClientVaultStorage,
7    },
8    ClientEventLogStorage, Error, Result,
9};
10use async_trait::async_trait;
11use indexmap::IndexSet;
12use sos_backend::{
13    AccountEventLog, BackendTarget, DeviceEventLog, Folder, FolderEventLog,
14};
15use sos_core::{
16    device::TrustedDevice,
17    events::{
18        patch::{AccountDiff, CheckedPatch, DeviceDiff, FolderDiff},
19        Event, ReadEvent, WriteEvent,
20    },
21    AccountId, Paths, SecretId, VaultId,
22};
23use sos_login::Identity;
24use sos_reducers::FolderReducer;
25use sos_sync::{
26    CreateSet, ForceMerge, Merge, MergeOutcome, StorageEventLogs, SyncStorage,
27};
28use sos_vault::{Summary, Vault};
29use std::{
30    collections::{HashMap, HashSet},
31    sync::Arc,
32};
33use tokio::sync::RwLock;
34
35#[cfg(feature = "search")]
36use sos_search::AccountSearch;
37
38#[cfg(feature = "files")]
39use {
40    crate::files::ExternalFileManager, sos_backend::FileEventLog,
41    sos_core::events::patch::FileDiff,
42};
43
44use crate::sync::SyncImpl;
45
46/// Client account storage.
47pub enum ClientStorage {
48    /// Filesystem storage.
49    FileSystem(SyncImpl<ClientFileSystemStorage>),
50    /// Database storage.
51    Database(SyncImpl<ClientDatabaseStorage>),
52}
53
54impl ClientStorage {
55    /// Create new client storage.
56    pub async fn new_unauthenticated(
57        target: BackendTarget,
58        account_id: &AccountId,
59    ) -> Result<Self> {
60        Ok(match &target {
61            BackendTarget::FileSystem(paths) => {
62                debug_assert!(!paths.is_server());
63                Self::FileSystem(SyncImpl::new(
64                    ClientFileSystemStorage::new_unauthenticated(
65                        target, account_id,
66                    )
67                    .await?,
68                ))
69            }
70            BackendTarget::Database(paths, _) => {
71                debug_assert!(!paths.is_server());
72                Self::Database(SyncImpl::new(
73                    ClientDatabaseStorage::new_unauthenticated(
74                        target, account_id,
75                    )
76                    .await?,
77                ))
78            }
79        })
80    }
81
82    /// Create a new account in client storage.
83    ///
84    /// The account must not already exist.
85    ///
86    /// Only to be used when fetching account data from a
87    /// remote during device enrollment.
88    pub async fn new_account(
89        target: BackendTarget,
90        account_id: &AccountId,
91        account_name: String,
92    ) -> Result<Self> {
93        Ok(match &target {
94            BackendTarget::FileSystem(paths) => {
95                debug_assert!(!paths.is_server());
96                Self::FileSystem(SyncImpl::new(
97                    ClientFileSystemStorage::new_account(
98                        target,
99                        account_id,
100                        account_name,
101                    )
102                    .await?,
103                ))
104            }
105            BackendTarget::Database(paths, _) => {
106                debug_assert!(!paths.is_server());
107                Self::Database(SyncImpl::new(
108                    ClientDatabaseStorage::new_account(
109                        target,
110                        account_id,
111                        account_name,
112                    )
113                    .await?,
114                ))
115            }
116        })
117    }
118
119    /// Rebuild the vault for a folder from event logs.
120    ///
121    /// Can be used to repair a vault from a collection of
122    /// event logs or to convert server-side head-only vaults
123    /// into the client-side representation.
124    ///
125    /// # Authentication
126    ///
127    /// Can be called when the account is not authenticated.
128    pub async fn rebuild_folder_vault(
129        target: BackendTarget,
130        account_id: &AccountId,
131        folder_id: &VaultId,
132    ) -> Result<Vec<u8>> {
133        let storage =
134            ClientStorage::new_unauthenticated(target, account_id).await?;
135
136        let event_log = FolderEventLog::new_folder(
137            storage.backend_target().clone(),
138            &account_id,
139            folder_id,
140        )
141        .await?;
142
143        let vault = FolderReducer::new()
144            .reduce(&event_log)
145            .await?
146            .build(true)
147            .await?;
148
149        storage.write_vault(&vault, Internal).await
150    }
151}
152
153impl ClientBaseStorage for ClientStorage {
154    fn account_id(&self) -> &AccountId {
155        match self {
156            ClientStorage::FileSystem(fs) => fs.account_id(),
157            ClientStorage::Database(db) => db.account_id(),
158        }
159    }
160
161    fn authenticated_user(&self) -> Option<&Identity> {
162        match self {
163            ClientStorage::FileSystem(fs) => fs.authenticated_user(),
164            ClientStorage::Database(db) => db.authenticated_user(),
165        }
166    }
167
168    fn authenticated_user_mut(&mut self) -> Option<&mut Identity> {
169        match self {
170            ClientStorage::FileSystem(fs) => fs.authenticated_user_mut(),
171            ClientStorage::Database(db) => db.authenticated_user_mut(),
172        }
173    }
174
175    fn paths(&self) -> Arc<Paths> {
176        match self {
177            ClientStorage::FileSystem(fs) => fs.paths(),
178            ClientStorage::Database(db) => db.paths(),
179        }
180    }
181
182    fn backend_target(&self) -> &BackendTarget {
183        match self {
184            ClientStorage::FileSystem(fs) => fs.backend_target(),
185            ClientStorage::Database(db) => db.backend_target(),
186        }
187    }
188
189    fn set_authenticated_user(
190        &mut self,
191        user: Option<Identity>,
192        token: Internal,
193    ) {
194        match self {
195            ClientStorage::FileSystem(fs) => {
196                fs.set_authenticated_user(user, token)
197            }
198            ClientStorage::Database(db) => {
199                db.set_authenticated_user(user, token)
200            }
201        }
202    }
203}
204
205#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
206#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
207impl ClientVaultStorage for ClientStorage {
208    async fn write_vault(
209        &self,
210        vault: &Vault,
211        token: Internal,
212    ) -> Result<Vec<u8>> {
213        match self {
214            ClientStorage::FileSystem(fs) => {
215                fs.write_vault(vault, token).await
216            }
217            ClientStorage::Database(db) => db.write_vault(vault, token).await,
218        }
219    }
220
221    async fn write_login_vault(
222        &self,
223        vault: &Vault,
224        token: Internal,
225    ) -> Result<Vec<u8>> {
226        match self {
227            ClientStorage::FileSystem(fs) => {
228                fs.write_login_vault(vault, token).await
229            }
230            ClientStorage::Database(db) => {
231                db.write_login_vault(vault, token).await
232            }
233        }
234    }
235
236    async fn remove_vault(
237        &self,
238        id: &VaultId,
239        token: Internal,
240    ) -> Result<()> {
241        match self {
242            ClientStorage::FileSystem(fs) => fs.remove_vault(id, token).await,
243            ClientStorage::Database(db) => db.remove_vault(id, token).await,
244        }
245    }
246
247    async fn read_vaults(&self, token: Internal) -> Result<Vec<Summary>> {
248        match self {
249            ClientStorage::FileSystem(fs) => fs.read_vaults(token).await,
250            ClientStorage::Database(db) => db.read_vaults(token).await,
251        }
252    }
253
254    fn summaries(&self, token: Internal) -> &Vec<Summary> {
255        match self {
256            ClientStorage::FileSystem(fs) => fs.summaries(token),
257            ClientStorage::Database(db) => db.summaries(token),
258        }
259    }
260
261    fn summaries_mut(&mut self, token: Internal) -> &mut Vec<Summary> {
262        match self {
263            ClientStorage::FileSystem(fs) => fs.summaries_mut(token),
264            ClientStorage::Database(db) => db.summaries_mut(token),
265        }
266    }
267}
268
269#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
270#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
271impl ClientFolderStorage for ClientStorage {
272    fn folders(&self) -> &HashMap<VaultId, Folder> {
273        match self {
274            ClientStorage::FileSystem(fs) => fs.folders(),
275            ClientStorage::Database(db) => db.folders(),
276        }
277    }
278
279    fn folders_mut(&mut self) -> &mut HashMap<VaultId, Folder> {
280        match self {
281            ClientStorage::FileSystem(fs) => fs.folders_mut(),
282            ClientStorage::Database(db) => db.folders_mut(),
283        }
284    }
285
286    async fn new_folder(
287        &self,
288        vault: &Vault,
289        token: Internal,
290    ) -> Result<Folder> {
291        match self {
292            ClientStorage::FileSystem(fs) => {
293                fs.new_folder(vault, token).await
294            }
295            ClientStorage::Database(db) => db.new_folder(vault, token).await,
296        }
297    }
298
299    async fn read_vault(&self, id: &VaultId) -> Result<Vault> {
300        match self {
301            ClientStorage::FileSystem(fs) => fs.read_vault(id).await,
302            ClientStorage::Database(db) => db.read_vault(id).await,
303        }
304    }
305
306    async fn read_login_vault(&self) -> Result<Vault> {
307        match self {
308            ClientStorage::FileSystem(fs) => fs.read_login_vault().await,
309            ClientStorage::Database(db) => db.read_login_vault().await,
310        }
311    }
312
313    fn current_folder(&self) -> Option<Summary> {
314        match self {
315            ClientStorage::FileSystem(fs) => fs.current_folder(),
316            ClientStorage::Database(db) => db.current_folder(),
317        }
318    }
319
320    fn open_folder(&self, folder_id: &VaultId) -> Result<ReadEvent> {
321        match self {
322            ClientStorage::FileSystem(fs) => fs.open_folder(folder_id),
323            ClientStorage::Database(db) => db.open_folder(folder_id),
324        }
325    }
326
327    fn close_folder(&self) {
328        match self {
329            ClientStorage::FileSystem(fs) => fs.close_folder(),
330            ClientStorage::Database(db) => db.close_folder(),
331        }
332    }
333}
334
335#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
336#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
337impl ClientDeviceStorage for ClientStorage {
338    fn devices(&self) -> &IndexSet<TrustedDevice> {
339        match self {
340            ClientStorage::FileSystem(fs) => fs.devices(),
341            ClientStorage::Database(db) => db.devices(),
342        }
343    }
344
345    fn set_devices(
346        &mut self,
347        devices: IndexSet<TrustedDevice>,
348        token: Internal,
349    ) {
350        match self {
351            ClientStorage::FileSystem(fs) => fs.set_devices(devices, token),
352            ClientStorage::Database(db) => db.set_devices(devices, token),
353        }
354    }
355
356    fn list_trusted_devices(&self) -> Vec<&TrustedDevice> {
357        match self {
358            ClientStorage::FileSystem(fs) => fs.list_trusted_devices(),
359            ClientStorage::Database(db) => db.list_trusted_devices(),
360        }
361    }
362}
363
364#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
365#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
366impl ClientAccountStorage for ClientStorage {
367    async fn import_account(
368        &mut self,
369        account_data: &CreateSet,
370    ) -> Result<()> {
371        match self {
372            ClientStorage::FileSystem(fs) => {
373                fs.import_account(account_data).await
374            }
375            ClientStorage::Database(db) => {
376                db.import_account(account_data).await
377            }
378        }
379    }
380
381    async fn list_secret_ids(
382        &self,
383        folder_id: &VaultId,
384    ) -> Result<Vec<SecretId>> {
385        self.guard_authenticated(Internal)?;
386
387        match self {
388            ClientStorage::FileSystem(fs) => {
389                fs.list_secret_ids(folder_id).await
390            }
391            ClientStorage::Database(db) => {
392                db.list_secret_ids(folder_id).await
393            }
394        }
395    }
396
397    async fn create_device_vault(
398        &mut self,
399        device_vault: &[u8],
400    ) -> Result<()> {
401        match self {
402            ClientStorage::FileSystem(fs) => {
403                fs.create_device_vault(device_vault).await
404            }
405            ClientStorage::Database(db) => {
406                db.create_device_vault(device_vault).await
407            }
408        }
409    }
410
411    async fn delete_account(&self) -> Result<Event> {
412        self.guard_authenticated(Internal)?;
413
414        match self {
415            ClientStorage::FileSystem(fs) => fs.delete_account().await,
416            ClientStorage::Database(db) => db.delete_account().await,
417        }
418    }
419
420    #[cfg(feature = "files")]
421    fn external_file_manager(&self) -> Option<&ExternalFileManager> {
422        match self {
423            ClientStorage::FileSystem(fs) => fs.external_file_manager(),
424            ClientStorage::Database(db) => db.external_file_manager(),
425        }
426    }
427
428    #[cfg(feature = "files")]
429    fn external_file_manager_mut(
430        &mut self,
431    ) -> Option<&mut ExternalFileManager> {
432        match self {
433            ClientStorage::FileSystem(fs) => fs.external_file_manager_mut(),
434            ClientStorage::Database(db) => db.external_file_manager_mut(),
435        }
436    }
437
438    #[cfg(feature = "files")]
439    fn set_external_file_manager(
440        &mut self,
441        file_manager: Option<ExternalFileManager>,
442        token: Internal,
443    ) {
444        match self {
445            ClientStorage::FileSystem(fs) => {
446                fs.set_external_file_manager(file_manager, token)
447            }
448            ClientStorage::Database(db) => {
449                db.set_external_file_manager(file_manager, token)
450            }
451        }
452    }
453
454    #[cfg(feature = "search")]
455    fn search_index(&self) -> Option<&AccountSearch> {
456        match self {
457            ClientStorage::FileSystem(fs) => fs.search_index(),
458            ClientStorage::Database(db) => db.search_index(),
459        }
460    }
461
462    #[cfg(feature = "search")]
463    fn search_index_mut(&mut self) -> Option<&mut AccountSearch> {
464        match self {
465            ClientStorage::FileSystem(fs) => fs.search_index_mut(),
466            ClientStorage::Database(db) => db.search_index_mut(),
467        }
468    }
469
470    #[cfg(feature = "search")]
471    fn set_search_index(
472        &mut self,
473        index: Option<AccountSearch>,
474        token: Internal,
475    ) {
476        match self {
477            ClientStorage::FileSystem(fs) => {
478                fs.set_search_index(index, token)
479            }
480            ClientStorage::Database(db) => db.set_search_index(index, token),
481        }
482    }
483}
484
485#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
486#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
487impl ClientEventLogStorage for ClientStorage {
488    async fn initialize_device_log(
489        &self,
490        device: TrustedDevice,
491        token: Internal,
492    ) -> Result<(DeviceEventLog, IndexSet<TrustedDevice>)> {
493        match self {
494            ClientStorage::FileSystem(fs) => {
495                fs.initialize_device_log(device, token).await
496            }
497            ClientStorage::Database(db) => {
498                db.initialize_device_log(device, token).await
499            }
500        }
501    }
502
503    #[cfg(feature = "files")]
504    async fn initialize_file_log(
505        &self,
506        token: Internal,
507    ) -> Result<FileEventLog> {
508        match self {
509            ClientStorage::FileSystem(fs) => {
510                fs.initialize_file_log(token).await
511            }
512            ClientStorage::Database(db) => {
513                db.initialize_file_log(token).await
514            }
515        }
516    }
517
518    fn set_identity_log(
519        &mut self,
520        log: Arc<RwLock<FolderEventLog>>,
521        token: Internal,
522    ) {
523        match self {
524            ClientStorage::FileSystem(fs) => fs.set_identity_log(log, token),
525            ClientStorage::Database(db) => db.set_identity_log(log, token),
526        }
527    }
528
529    fn set_device_log(
530        &mut self,
531        log: Arc<RwLock<DeviceEventLog>>,
532        token: Internal,
533    ) {
534        match self {
535            ClientStorage::FileSystem(fs) => fs.set_device_log(log, token),
536            ClientStorage::Database(db) => db.set_device_log(log, token),
537        }
538    }
539
540    #[cfg(feature = "files")]
541    fn set_file_log(
542        &mut self,
543        log: Arc<RwLock<FileEventLog>>,
544        token: Internal,
545    ) {
546        match self {
547            ClientStorage::FileSystem(fs) => fs.set_file_log(log, token),
548            ClientStorage::Database(db) => db.set_file_log(log, token),
549        }
550    }
551}
552
553#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
554#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
555impl StorageEventLogs for ClientStorage {
556    type Error = Error;
557
558    async fn identity_log(&self) -> Result<Arc<RwLock<FolderEventLog>>> {
559        match self {
560            ClientStorage::FileSystem(fs) => fs.identity_log().await,
561            ClientStorage::Database(db) => db.identity_log().await,
562        }
563    }
564
565    async fn account_log(&self) -> Result<Arc<RwLock<AccountEventLog>>> {
566        match self {
567            ClientStorage::FileSystem(fs) => fs.account_log().await,
568            ClientStorage::Database(db) => db.account_log().await,
569        }
570    }
571
572    async fn device_log(&self) -> Result<Arc<RwLock<DeviceEventLog>>> {
573        match self {
574            ClientStorage::FileSystem(fs) => fs.device_log().await,
575            ClientStorage::Database(db) => db.device_log().await,
576        }
577    }
578
579    #[cfg(feature = "files")]
580    async fn file_log(&self) -> Result<Arc<RwLock<FileEventLog>>> {
581        match self {
582            ClientStorage::FileSystem(fs) => fs.file_log().await,
583            ClientStorage::Database(db) => db.file_log().await,
584        }
585    }
586
587    async fn folder_details(&self) -> Result<IndexSet<Summary>> {
588        match self {
589            ClientStorage::FileSystem(fs) => fs.folder_details().await,
590            ClientStorage::Database(db) => db.folder_details().await,
591        }
592    }
593
594    async fn folder_log(
595        &self,
596        id: &VaultId,
597    ) -> Result<Arc<RwLock<FolderEventLog>>> {
598        match self {
599            ClientStorage::FileSystem(fs) => fs.folder_log(id).await,
600            ClientStorage::Database(db) => db.folder_log(id).await,
601        }
602    }
603}
604
605#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
606#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
607impl ForceMerge for ClientStorage {
608    async fn force_merge_identity(
609        &mut self,
610        diff: FolderDiff,
611        outcome: &mut MergeOutcome,
612    ) -> Result<()> {
613        match self {
614            ClientStorage::FileSystem(fs) => {
615                fs.force_merge_identity(diff, outcome).await
616            }
617            ClientStorage::Database(db) => {
618                db.force_merge_identity(diff, outcome).await
619            }
620        }
621    }
622
623    /// Force merge changes to the files event log.
624    async fn force_merge_folder(
625        &mut self,
626        folder_id: &VaultId,
627        diff: FolderDiff,
628        outcome: &mut MergeOutcome,
629    ) -> Result<()> {
630        match self {
631            ClientStorage::FileSystem(fs) => {
632                fs.force_merge_folder(folder_id, diff, outcome).await
633            }
634            ClientStorage::Database(db) => {
635                db.force_merge_folder(folder_id, diff, outcome).await
636            }
637        }
638    }
639}
640
641#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
642#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
643impl Merge for ClientStorage {
644    async fn merge_identity(
645        &mut self,
646        diff: FolderDiff,
647        outcome: &mut MergeOutcome,
648    ) -> Result<CheckedPatch> {
649        match self {
650            ClientStorage::FileSystem(fs) => {
651                fs.merge_identity(diff, outcome).await
652            }
653            ClientStorage::Database(db) => {
654                db.merge_identity(diff, outcome).await
655            }
656        }
657    }
658
659    async fn merge_account(
660        &mut self,
661        diff: AccountDiff,
662        outcome: &mut MergeOutcome,
663    ) -> Result<(CheckedPatch, HashSet<VaultId>)> {
664        match self {
665            ClientStorage::FileSystem(fs) => {
666                fs.merge_account(diff, outcome).await
667            }
668            ClientStorage::Database(db) => {
669                db.merge_account(diff, outcome).await
670            }
671        }
672    }
673
674    async fn merge_device(
675        &mut self,
676        diff: DeviceDiff,
677        outcome: &mut MergeOutcome,
678    ) -> Result<CheckedPatch> {
679        match self {
680            ClientStorage::FileSystem(fs) => {
681                fs.merge_device(diff, outcome).await
682            }
683            ClientStorage::Database(db) => {
684                db.merge_device(diff, outcome).await
685            }
686        }
687    }
688
689    #[cfg(feature = "files")]
690    async fn merge_files(
691        &mut self,
692        diff: FileDiff,
693        outcome: &mut MergeOutcome,
694    ) -> Result<CheckedPatch> {
695        match self {
696            ClientStorage::FileSystem(fs) => {
697                fs.merge_files(diff, outcome).await
698            }
699            ClientStorage::Database(db) => {
700                db.merge_files(diff, outcome).await
701            }
702        }
703    }
704
705    async fn merge_folder(
706        &mut self,
707        folder_id: &VaultId,
708        diff: FolderDiff,
709        outcome: &mut MergeOutcome,
710    ) -> Result<(CheckedPatch, Vec<WriteEvent>)> {
711        match self {
712            ClientStorage::FileSystem(fs) => {
713                fs.merge_folder(folder_id, diff, outcome).await
714            }
715            ClientStorage::Database(db) => {
716                db.merge_folder(folder_id, diff, outcome).await
717            }
718        }
719    }
720}
721
722#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
723#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
724impl SyncStorage for ClientStorage {
725    fn is_client_storage(&self) -> bool {
726        true
727    }
728}