1pub mod app_config;
40pub mod fec_storage;
41pub mod key_management;
42pub mod passkey;
43pub mod platform_storage;
44pub mod session;
45pub mod vault;
46
47use anyhow::{Context, Result};
48use serde::{Deserialize, Serialize};
49use std::collections::HashMap;
50use std::path::PathBuf;
51use std::sync::Arc;
52use tokio::sync::RwLock;
53
54pub use app_config::*;
55pub use fec_storage::*;
56pub use key_management::*;
57pub use passkey::*;
58pub use platform_storage::*;
59pub use session::*;
60pub use vault::*;
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct StorageConfig {
65 pub vault_dir: PathBuf,
67
68 pub pbkdf2_iterations: u32,
70
71 pub enable_fec: bool,
73
74 pub fec_redundancy: f32,
76
77 pub max_vault_size: u64,
79
80 pub use_keyring: bool,
82
83 pub cache_timeout: u64,
85}
86
87impl Default for StorageConfig {
88 fn default() -> Self {
89 Self {
90 vault_dir: get_vault_directory(),
91 pbkdf2_iterations: 100_000, enable_fec: true,
93 fec_redundancy: 1.5,
94 max_vault_size: 0, use_keyring: true,
96 cache_timeout: 300, }
98 }
99}
100
101pub struct EncryptedStorageManager {
103 config: StorageConfig,
104 vaults: Arc<RwLock<HashMap<String, Arc<EncryptedVault>>>>,
105 active_sessions: Arc<RwLock<HashMap<String, Session>>>,
106 key_manager: Arc<KeyManager>,
107 platform_storage: Arc<PlatformStorage>,
108 app_config: Arc<RwLock<AppConfigManager>>,
109 passkey_manager: Arc<PasskeyManager>,
110}
111
112impl EncryptedStorageManager {
113 pub async fn new(config: StorageConfig) -> Result<Self> {
115 let platform_storage = Arc::new(
117 PlatformStorage::new(&config.vault_dir)
118 .context("Failed to initialize platform storage")?,
119 );
120
121 let key_manager = Arc::new(
123 KeyManager::new(config.pbkdf2_iterations, config.use_keyring)
124 .await
125 .context("Failed to initialize key manager")?,
126 );
127
128 let config_dir = config
130 .vault_dir
131 .parent()
132 .unwrap_or(&config.vault_dir)
133 .to_path_buf();
134 let app_config = Arc::new(RwLock::new(
135 AppConfigManager::new(config_dir.clone())
136 .await
137 .context("Failed to initialize app config")?,
138 ));
139
140 let passkey_storage_dir = config_dir.join("passkeys");
142 let passkey_manager = Arc::new(
143 PasskeyManager::new(&passkey_storage_dir)
144 .context("Failed to initialize passkey manager")?,
145 );
146
147 Ok(Self {
148 config,
149 vaults: Arc::new(RwLock::new(HashMap::new())),
150 active_sessions: Arc::new(RwLock::new(HashMap::new())),
151 key_manager,
152 platform_storage,
153 app_config,
154 passkey_manager,
155 })
156 }
157
158 pub async fn create_vault(
160 &self,
161 four_words: &str,
162 password: &str,
163 display_name: &str,
164 ) -> Result<String> {
165 let normalized = self.normalize_four_words(four_words);
167
168 if self.vault_exists(&normalized).await? {
170 return Err(anyhow::anyhow!("Vault already exists for {}", four_words));
171 }
172
173 let salt = generate_salt();
175 let key = self
176 .key_manager
177 .derive_key(password, &salt)
178 .await
179 .context("Failed to derive encryption key")?;
180
181 let vault = EncryptedVault::create(
183 normalized.clone(),
184 display_name.to_string(),
185 key.clone(),
186 salt,
187 &self.config,
188 )
189 .await
190 .context("Failed to create vault")?;
191
192 let mut vaults = self.vaults.write().await;
194 vaults.insert(normalized.clone(), Arc::new(vault));
195
196 self.store_password_locator(&normalized, password).await?;
198
199 if self.config.use_keyring {
201 self.key_manager
202 .store_in_keyring(&normalized, &key)
203 .await
204 .ok(); }
206
207 Ok(normalized)
208 }
209
210 pub async fn login_password_only(&self, password: &str) -> Result<Session> {
212 let password_hash = self.key_manager.hash_password(password).await?;
214
215 let four_words = self
217 .platform_storage
218 .find_vault_by_password_hash(&password_hash)
219 .await
220 .context("No vault found for this password")?;
221
222 self.login(&four_words, password, None).await
224 }
225
226 pub async fn login(
228 &self,
229 four_words: &str,
230 password: &str,
231 _passkey: Option<Vec<u8>>,
232 ) -> Result<Session> {
233 let normalized = self.normalize_four_words(four_words);
234
235 let vault = self.load_vault(&normalized, password).await?;
237
238 let session = Session::new(
240 normalized.clone(),
241 vault.display_name.clone(),
242 self.config.cache_timeout,
243 );
244
245 let mut sessions = self.active_sessions.write().await;
247 sessions.insert(session.id.clone(), session.clone());
248
249 let mut app_config = self.app_config.write().await;
251 app_config
252 .set_last_identity(normalized.clone(), vault.display_name.clone())
253 .await
254 .ok(); if self.config.use_keyring && app_config.get_config().keyring_enabled {
258 tracing::info!(
259 "🔑 LOGIN: Attempting to store password in keyring for '{}'",
260 normalized
261 );
262 match self
263 .key_manager
264 .store_in_keyring(&normalized, password.as_bytes())
265 .await
266 {
267 Ok(()) => {
268 tracing::info!(
269 "✅ LOGIN: Password stored in keyring successfully for '{}'",
270 normalized
271 );
272 }
273 Err(e) => {
274 tracing::error!(
275 "❌ LOGIN: Failed to store password in keyring for '{}': {}",
276 normalized,
277 e
278 );
279 tracing::error!(
280 "⚠️ LOGIN: This means passkey/Touch ID authentication will fail later!"
281 );
282 }
283 }
284 } else {
285 tracing::warn!(
286 "⚠️ LOGIN: Keyring storage skipped - use_keyring={}, keyring_enabled={}",
287 self.config.use_keyring,
288 app_config.get_config().keyring_enabled
289 );
290 }
291
292 Ok(session)
293 }
294
295 pub async fn store(
297 &self,
298 session_id: &str,
299 key: &str,
300 data: &[u8],
301 use_fec: bool,
302 ) -> Result<()> {
303 let session = self.validate_session(session_id).await?;
305
306 let vaults = self.vaults.read().await;
308 let vault = vaults
309 .get(&session.four_words)
310 .ok_or_else(|| anyhow::anyhow!("Vault not loaded"))?;
311
312 if use_fec && self.config.enable_fec {
314 vault
316 .store_with_fec(key, data, self.config.fec_redundancy)
317 .await
318 } else {
319 vault.store(key, data).await
321 }
322 }
323
324 pub async fn retrieve(&self, session_id: &str, key: &str) -> Result<Vec<u8>> {
326 let session = self.validate_session(session_id).await?;
328
329 let vaults = self.vaults.read().await;
331 let vault = vaults
332 .get(&session.four_words)
333 .ok_or_else(|| anyhow::anyhow!("Vault not loaded"))?;
334
335 vault.retrieve(key).await
337 }
338
339 pub async fn list_vaults(&self) -> Result<Vec<VaultInfo>> {
341 self.platform_storage.list_vaults().await
342 }
343
344 pub async fn switch_account(&self, session_id: &str, four_words: &str) -> Result<Session> {
346 self.logout(session_id).await?;
348
349 if let Some(vault) = self.vaults.read().await.get(four_words) {
351 let session = Session::new(
353 four_words.to_string(),
354 vault.display_name.clone(),
355 self.config.cache_timeout,
356 );
357
358 let mut sessions = self.active_sessions.write().await;
359 sessions.insert(session.id.clone(), session.clone());
360
361 return Ok(session);
362 }
363
364 Err(anyhow::anyhow!("Vault not cached, password required"))
365 }
366
367 pub async fn logout(&self, session_id: &str) -> Result<()> {
369 let mut sessions = self.active_sessions.write().await;
370 sessions.remove(session_id);
371 Ok(())
372 }
373
374 pub async fn export_vault(&self, session_id: &str, include_data: bool) -> Result<Vec<u8>> {
376 let session = self.validate_session(session_id).await?;
377
378 let vaults = self.vaults.read().await;
379 let vault = vaults
380 .get(&session.four_words)
381 .ok_or_else(|| anyhow::anyhow!("Vault not loaded"))?;
382
383 vault.export(include_data).await
384 }
385
386 pub async fn import_vault(&self, backup_data: &[u8], password: &str) -> Result<String> {
388 let vault = EncryptedVault::import(backup_data, password, &self.config).await?;
389 let four_words = vault.four_words.clone();
390
391 let mut vaults = self.vaults.write().await;
392 vaults.insert(four_words.clone(), Arc::new(vault));
393
394 Ok(four_words)
395 }
396
397 pub async fn get_app_config(&self) -> AppConfig {
399 self.app_config.read().await.get_config().clone()
400 }
401
402 pub async fn try_auto_login(&self) -> Result<Option<Session>> {
404 let app_config = self.app_config.read().await;
405 let config = app_config.get_config();
406
407 if !config.auto_login_enabled {
409 return Ok(None);
410 }
411
412 let four_words = match &config.last_identity {
414 Some(fw) => fw.clone(),
415 None => return Ok(None),
416 };
417
418 drop(app_config); if self.config.use_keyring
422 && let Ok(password_bytes) = self.key_manager.get_from_keyring(&four_words).await
423 && let Ok(password) = String::from_utf8(password_bytes.to_vec())
424 {
425 match self.login(&four_words, &password, None).await {
427 Ok(session) => return Ok(Some(session)),
428 Err(_) => {
429 self.key_manager.delete_from_keyring(&four_words).await.ok();
432 }
433 }
434 }
435
436 Ok(None)
437 }
438
439 pub async fn set_auto_login_enabled(&self, enabled: bool) -> Result<()> {
441 let mut app_config = self.app_config.write().await;
442 app_config.set_auto_login(enabled).await
443 }
444
445 pub async fn set_keyring_enabled(&self, enabled: bool) -> Result<()> {
447 let mut app_config = self.app_config.write().await;
448 app_config.set_keyring_enabled(enabled).await
449 }
450
451 pub async fn get_recent_identities(&self) -> Vec<RecentIdentity> {
453 self.app_config
454 .read()
455 .await
456 .get_config()
457 .recent_identities
458 .clone()
459 }
460
461 pub async fn remove_recent_identity(&self, four_words: &str) -> Result<()> {
463 self.app_config
464 .write()
465 .await
466 .remove_recent_identity(four_words)
467 .await
468 }
469
470 pub async fn passkey_register(
474 &self,
475 four_words: &str,
476 device_name: &str,
477 ) -> Result<PasskeyInfo> {
478 let normalized = self.normalize_four_words(four_words);
479
480 let info = self
482 .passkey_manager
483 .register_passkey(&normalized, device_name)
484 .await?;
485
486 let mut app_config = self.app_config.write().await;
488 app_config
489 .set_identity_has_passkey(&normalized, true)
490 .await?;
491
492 Ok(info)
493 }
494
495 pub async fn passkey_register_webauthn(
499 &self,
500 four_words: &str,
501 device_name: &str,
502 credential: WebAuthnCredential,
503 ) -> Result<PasskeyInfo> {
504 let normalized = self.normalize_four_words(four_words);
505
506 let info = self
508 .passkey_manager
509 .register_passkey_webauthn(&normalized, device_name, credential)
510 .await?;
511
512 let mut app_config = self.app_config.write().await;
514 app_config
515 .set_identity_has_passkey(&normalized, true)
516 .await?;
517
518 Ok(info)
519 }
520
521 pub async fn passkey_authenticate(&self, four_words: &str) -> Result<Session> {
525 let normalized = self.normalize_four_words(four_words);
526
527 tracing::info!(
528 "🔍 RETRIEVAL: Attempting passkey auth for four_words='{}' -> normalized='{}'",
529 four_words,
530 normalized
531 );
532
533 if !self.passkey_manager.has_passkey(&normalized).await {
535 tracing::error!("❌ RETRIEVAL: No passkey registered for '{}'", normalized);
536 anyhow::bail!("No passkey registered for this identity");
537 }
538
539 tracing::info!("✅ RETRIEVAL: Passkey IS registered for '{}'", normalized);
540
541 self.passkey_manager
543 .mark_passkey_used(&normalized)
544 .await
545 .ok();
546
547 tracing::info!(
549 "🔍 RETRIEVAL: Checking keyring config - use_keyring={}",
550 self.config.use_keyring
551 );
552
553 if self.config.use_keyring {
554 tracing::info!(
555 "🔍 RETRIEVAL: Attempting to get password from keyring for '{}'",
556 normalized
557 );
558
559 match self.key_manager.get_from_keyring(&normalized).await {
560 Ok(password_bytes) => {
561 tracing::info!(
562 "✅ RETRIEVAL: Password bytes retrieved from keyring for '{}'",
563 normalized
564 );
565
566 match String::from_utf8(password_bytes.to_vec()) {
567 Ok(password) => {
568 tracing::info!(
569 "✅ RETRIEVAL: Password successfully decoded, attempting login for '{}'",
570 normalized
571 );
572 return self.login(&normalized, &password, None).await;
574 }
575 Err(e) => {
576 tracing::error!(
577 "❌ RETRIEVAL: Failed to decode password bytes for '{}': {}",
578 normalized,
579 e
580 );
581 }
582 }
583 }
584 Err(e) => {
585 tracing::error!(
586 "❌ RETRIEVAL: Failed to get password from keyring for '{}': {}",
587 normalized,
588 e
589 );
590 }
591 }
592 } else {
593 tracing::warn!("⚠️ RETRIEVAL: Keyring is disabled in config");
594 }
595
596 tracing::error!(
598 "❌ RETRIEVAL: No password found in keyring for '{}'",
599 normalized
600 );
601 anyhow::bail!(
602 "Passkey registered but vault password not found in keyring. Please login with password first."
603 )
604 }
605
606 pub async fn passkey_has_passkey(&self, four_words: &str) -> bool {
608 let normalized = self.normalize_four_words(four_words);
609 self.passkey_manager.has_passkey(&normalized).await
610 }
611
612 pub async fn passkey_get_info(&self, four_words: &str) -> Result<PasskeyInfo> {
614 let normalized = self.normalize_four_words(four_words);
615 self.passkey_manager.get_passkey_info(&normalized).await
616 }
617
618 pub async fn passkey_delete(&self, four_words: &str) -> Result<()> {
620 let normalized = self.normalize_four_words(four_words);
621 self.passkey_manager.delete_passkey(&normalized).await?;
622
623 let mut app_config = self.app_config.write().await;
625 app_config
626 .set_identity_has_passkey(&normalized, false)
627 .await?;
628
629 Ok(())
630 }
631
632 pub async fn delete_vault(&self, four_words: &str) -> Result<()> {
636 let normalized = self.normalize_four_words(four_words);
637
638 let mut vaults = self.vaults.write().await;
640 vaults.remove(&normalized);
641 drop(vaults);
642
643 let vault_path = self.config.vault_dir.join(&normalized);
645 if vault_path.exists() {
646 tokio::fs::remove_dir_all(&vault_path)
647 .await
648 .map_err(|e| anyhow::anyhow!("Failed to delete vault directory: {}", e))?;
649 }
650
651 self.key_manager.delete_from_keyring(&normalized).await.ok();
653
654 self.passkey_manager.delete_passkey(&normalized).await.ok();
656
657 let mut app_config = self.app_config.write().await;
659 app_config.remove_recent_identity(&normalized).await?;
660
661 Ok(())
662 }
663
664 pub async fn store_password_in_keyring(&self, four_words: &str, password: &str) -> Result<()> {
666 let normalized = self.normalize_four_words(four_words);
667 tracing::info!(
668 "🔑 STORAGE: Storing password in keyring for four_words='{}' -> normalized='{}'",
669 four_words,
670 normalized
671 );
672 let result = self
673 .key_manager
674 .store_in_keyring(&normalized, password.as_bytes())
675 .await;
676
677 if result.is_ok() {
678 tracing::info!(
679 "✅ STORAGE: Password successfully stored in keyring for '{}'",
680 normalized
681 );
682 } else {
683 tracing::error!(
684 "❌ STORAGE: Failed to store password in keyring for '{}': {:?}",
685 normalized,
686 result
687 );
688 }
689
690 result
691 }
692
693 pub async fn remove_password_from_keyring(&self, four_words: &str) -> Result<()> {
695 let normalized = self.normalize_four_words(four_words);
696 self.key_manager.delete_from_keyring(&normalized).await
697 }
698
699 fn normalize_four_words(&self, four_words: &str) -> String {
702 four_words.trim().to_lowercase().replace([' ', '_'], "-")
703 }
704
705 pub async fn vault_exists(&self, four_words: &str) -> Result<bool> {
706 self.platform_storage.vault_exists(four_words).await
707 }
708
709 async fn load_vault(&self, four_words: &str, password: &str) -> Result<Arc<EncryptedVault>> {
710 let vault = EncryptedVault::load(four_words, password, &self.config).await?;
716
717 let mut vaults = self.vaults.write().await;
719 let vault_arc = Arc::new(vault);
720 vaults.insert(four_words.to_string(), vault_arc.clone());
721
722 Ok(vault_arc)
723 }
724
725 async fn store_password_locator(&self, four_words: &str, password: &str) -> Result<()> {
726 let password_hash = self.key_manager.hash_password(password).await?;
727 self.platform_storage
728 .store_password_locator(&password_hash, four_words)
729 .await
730 }
731
732 async fn validate_session(&self, session_id: &str) -> Result<Session> {
733 let sessions = self.active_sessions.read().await;
734 let session = sessions
735 .get(session_id)
736 .ok_or_else(|| anyhow::anyhow!("Invalid or expired session"))?;
737
738 if session.is_expired() {
739 return Err(anyhow::anyhow!("Session expired"));
740 }
741
742 Ok(session.clone())
743 }
744}
745
746fn get_vault_directory() -> PathBuf {
748 #[cfg(target_os = "macos")]
749 {
750 dirs::home_dir()
751 .unwrap_or_else(|| PathBuf::from("/tmp"))
752 .join("Library")
753 .join("Application Support")
754 .join("com.saorsalabs.communitas")
755 .join("vaults")
756 }
757
758 #[cfg(target_os = "windows")]
759 {
760 dirs::config_dir()
761 .unwrap_or_else(|| PathBuf::from("C:\\ProgramData"))
762 .join("communitas")
763 .join("vaults")
764 }
765
766 #[cfg(target_os = "linux")]
767 {
768 dirs::config_dir()
769 .unwrap_or_else(|| PathBuf::from("/tmp"))
770 .join("communitas")
771 .join("vaults")
772 }
773
774 #[cfg(target_os = "ios")]
775 {
776 dirs::document_dir()
778 .unwrap_or_else(|| PathBuf::from("/var/mobile/Containers/Data/Application"))
779 .join("communitas")
780 .join("vaults")
781 }
782
783 #[cfg(not(any(
784 target_os = "macos",
785 target_os = "windows",
786 target_os = "linux",
787 target_os = "ios"
788 )))]
789 {
790 PathBuf::from("/tmp").join("communitas").join("vaults")
792 }
793}
794
795fn generate_salt() -> Vec<u8> {
797 use rand::{Rng, SeedableRng};
798 let mut salt = vec![0u8; 32];
799 rand::rngs::StdRng::from_entropy().fill(&mut salt[..]);
800 salt
801}
802
803#[derive(Debug, Clone, Serialize, Deserialize)]
805pub struct VaultInfo {
806 pub four_words: String,
807 pub display_name: String,
808 pub created_at: u64,
809 pub last_accessed: u64,
810 pub size_bytes: u64,
811}
812
813#[cfg(test)]
814mod tests {
815 use super::*;
816 use tempfile::TempDir;
817
818 #[tokio::test]
819 async fn test_vault_creation_and_login() {
820 let temp_dir = TempDir::new().unwrap();
821 let config = StorageConfig {
822 vault_dir: temp_dir.path().to_path_buf(),
823 use_keyring: false, ..Default::default()
825 };
826
827 let manager = EncryptedStorageManager::new(config).await.unwrap();
828
829 let four_words = manager
831 .create_vault("ocean-forest-moon-star", "test_password", "Alice")
832 .await
833 .unwrap();
834
835 assert_eq!(four_words, "ocean-forest-moon-star");
836
837 let session = manager
839 .login("ocean-forest-moon-star", "test_password", None)
840 .await
841 .unwrap();
842
843 assert_eq!(session.four_words, "ocean-forest-moon-star");
844
845 let test_data = b"Hello, encrypted world!";
847 manager
848 .store(&session.id, "test_key", test_data, false)
849 .await
850 .unwrap();
851
852 let retrieved = manager.retrieve(&session.id, "test_key").await.unwrap();
853 assert_eq!(retrieved, test_data);
854 }
855
856 #[tokio::test]
857 async fn test_password_only_login() {
858 let temp_dir = TempDir::new().unwrap();
859 let config = StorageConfig {
860 vault_dir: temp_dir.path().to_path_buf(),
861 use_keyring: false,
862 ..Default::default()
863 };
864
865 let manager = EncryptedStorageManager::new(config).await.unwrap();
866
867 manager
869 .create_vault("river-mountain-sun-cloud", "unique_password", "Bob")
870 .await
871 .unwrap();
872
873 let session = manager
878 .login_password_only("unique_password")
879 .await
880 .unwrap();
881
882 assert_eq!(session.four_words, "river-mountain-sun-cloud");
883 }
884
885 #[tokio::test]
886 async fn test_fec_storage() {
887 let temp_dir = TempDir::new().unwrap();
888 let config = StorageConfig {
889 vault_dir: temp_dir.path().to_path_buf(),
890 use_keyring: false,
891 enable_fec: true,
892 fec_redundancy: 2.0, ..Default::default()
894 };
895
896 let manager = EncryptedStorageManager::new(config).await.unwrap();
897
898 manager
900 .create_vault("test-fec-vault-storage", "password", "FEC Test")
901 .await
902 .unwrap();
903
904 let session = manager
905 .login("test-fec-vault-storage", "password", None)
906 .await
907 .unwrap();
908
909 let large_data = vec![42u8; 1024 * 1024]; manager
912 .store(&session.id, "large_file", &large_data, true)
913 .await
914 .unwrap();
915
916 let retrieved = manager.retrieve(&session.id, "large_file").await.unwrap();
918 assert_eq!(retrieved, large_data);
919 }
920}