1use crate::{
4 config::StorageConfig,
5 models::{ApiKey, SecureApiKey},
6};
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use thiserror::Error;
12use tokio::fs;
13use tracing::{debug, error, info, warn};
14
15#[derive(Debug, Error)]
16pub enum StorageError {
17 #[error("Storage error: {0}")]
18 General(String),
19
20 #[error("File I/O error: {0}")]
21 Io(#[from] std::io::Error),
22
23 #[error("Serialization error: {0}")]
24 Serialization(#[from] serde_json::Error),
25
26 #[error("Permission error: {0}")]
27 Permission(String),
28
29 #[error("Encryption error: {0}")]
30 Encryption(#[from] crate::crypto::encryption::EncryptionError),
31}
32
33#[async_trait]
35pub trait StorageBackend: Send + Sync {
36 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError>;
37 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError>;
38 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError>;
39 async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError>;
40}
41
42pub async fn create_storage_backend(
44 config: &StorageConfig,
45) -> Result<Arc<dyn StorageBackend>, StorageError> {
46 match config {
47 StorageConfig::File {
48 path,
49 file_permissions,
50 dir_permissions,
51 require_secure_filesystem,
52 enable_filesystem_monitoring,
53 } => {
54 let storage = FileStorage::new(
55 path.clone(),
56 *file_permissions,
57 *dir_permissions,
58 *require_secure_filesystem,
59 *enable_filesystem_monitoring,
60 )
61 .await?;
62 Ok(Arc::new(storage))
63 }
64 StorageConfig::Environment { prefix } => {
65 let storage = EnvironmentStorage::new(prefix.clone());
66 Ok(Arc::new(storage))
67 }
68 StorageConfig::Memory => {
69 let storage = MemoryStorage::new();
70 Ok(Arc::new(storage))
71 }
72 }
73}
74
75pub struct FileStorage {
77 path: PathBuf,
78 encryption_key: [u8; 32],
79 file_permissions: u32,
80 #[allow(dead_code)]
81 dir_permissions: u32,
82 #[allow(dead_code)]
83 require_secure_filesystem: bool,
84 enable_filesystem_monitoring: bool,
85 write_mutex: tokio::sync::Mutex<()>,
86}
87
88impl FileStorage {
89 pub async fn new(
90 path: PathBuf,
91 file_permissions: u32,
92 dir_permissions: u32,
93 require_secure_filesystem: bool,
94 enable_filesystem_monitoring: bool,
95 ) -> Result<Self, StorageError> {
96 use crate::crypto::encryption::derive_encryption_key;
97 use crate::crypto::keys::generate_master_key;
98
99 if require_secure_filesystem {
101 Self::validate_filesystem_security(&path).await?;
102 }
103
104 if let Some(parent) = path.parent() {
106 fs::create_dir_all(parent).await?;
107
108 #[cfg(unix)]
110 {
111 use std::os::unix::fs::PermissionsExt;
112 let mut perms = fs::metadata(parent).await?.permissions();
113 perms.set_mode(dir_permissions); fs::set_permissions(parent, perms).await?;
115
116 Self::verify_directory_security(parent, dir_permissions).await?;
118 }
119 }
120
121 let master_key = generate_master_key().map_err(|e| StorageError::General(e.to_string()))?;
123 let encryption_key = derive_encryption_key(&master_key, "api-key-storage");
124
125 let storage = Self {
126 path,
127 encryption_key,
128 file_permissions,
129 dir_permissions,
130 require_secure_filesystem,
131 enable_filesystem_monitoring,
132 write_mutex: tokio::sync::Mutex::new(()),
133 };
134
135 if !storage.path.exists() {
137 storage.save_all_keys(&HashMap::new()).await?;
138 } else {
139 storage.ensure_secure_permissions().await?;
141 }
142
143 Ok(storage)
144 }
145
146 async fn ensure_secure_permissions(&self) -> Result<(), StorageError> {
147 #[cfg(unix)]
148 {
149 use std::os::unix::fs::PermissionsExt;
150
151 if self.path.exists() {
152 let metadata = fs::metadata(&self.path).await?;
153 let mode = metadata.permissions().mode() & 0o777;
154
155 if mode != self.file_permissions {
157 warn!(
158 "Incorrect permissions on key file: {:o}, fixing to {:o}",
159 mode, self.file_permissions
160 );
161 let mut perms = metadata.permissions();
162 perms.set_mode(self.file_permissions);
163 fs::set_permissions(&self.path, perms).await?;
164 }
165
166 Self::verify_file_ownership(&self.path).await?;
168 }
169 }
170 Ok(())
171 }
172
173 async fn validate_filesystem_security(path: &PathBuf) -> Result<(), StorageError> {
175 #[cfg(unix)]
176 {
177 use std::os::unix::fs::MetadataExt;
178
179 if let Some(parent) = path.parent() {
180 if parent.exists() {
181 let metadata = fs::metadata(parent).await?;
182
183 let _dev = metadata.dev();
185
186 if let Ok(mount_info) = fs::read_to_string("/proc/mounts").await {
189 let path_str = parent.to_string_lossy();
190 for line in mount_info.lines() {
191 let parts: Vec<&str> = line.split_whitespace().collect();
192 if parts.len() >= 3 {
193 let mount_point = parts[1];
194 let fs_type = parts[2];
195
196 if path_str.starts_with(mount_point) {
197 match fs_type {
199 "nfs" | "nfs4" | "cifs" | "smb" | "smbfs"
200 | "fuse.sshfs" => {
201 return Err(StorageError::Permission(format!(
202 "Storage path {} is on insecure network filesystem: {}",
203 path_str, fs_type
204 )));
205 }
206 _ => {}
207 }
208 }
209 }
210 }
211 }
212 }
213 }
214 }
215
216 Ok(())
217 }
218
219 async fn verify_directory_security(
221 dir: &std::path::Path,
222 expected_perms: u32,
223 ) -> Result<(), StorageError> {
224 #[cfg(unix)]
225 {
226 use std::os::unix::fs::{MetadataExt, PermissionsExt};
227
228 let metadata = fs::metadata(dir).await?;
229 let mode = metadata.permissions().mode() & 0o777;
230
231 if (mode & !expected_perms) != 0 {
233 return Err(StorageError::Permission(format!(
234 "Directory {} has insecure permissions: {:o} (expected: {:o})",
235 dir.display(),
236 mode,
237 expected_perms
238 )));
239 }
240
241 let current_uid = unsafe { libc::getuid() };
243 if metadata.uid() != current_uid {
244 return Err(StorageError::Permission(format!(
245 "Directory {} is not owned by current user (uid: {} vs {})",
246 dir.display(),
247 metadata.uid(),
248 current_uid
249 )));
250 }
251 }
252
253 Ok(())
254 }
255
256 async fn verify_file_ownership(file: &std::path::Path) -> Result<(), StorageError> {
258 #[cfg(unix)]
259 {
260 use std::os::unix::fs::MetadataExt;
261
262 let metadata = fs::metadata(file).await?;
263 let current_uid = unsafe { libc::getuid() };
264
265 if metadata.uid() != current_uid {
266 return Err(StorageError::Permission(format!(
267 "File {} is not owned by current user (uid: {} vs {})",
268 file.display(),
269 metadata.uid(),
270 current_uid
271 )));
272 }
273 }
274
275 Ok(())
276 }
277
278 async fn save_secure_keys(
280 &self,
281 keys: &HashMap<String, SecureApiKey>,
282 ) -> Result<(), StorageError> {
283 use crate::crypto::encryption::encrypt_data;
284
285 let content = serde_json::to_string_pretty(keys)?;
286 let encrypted_data = encrypt_data(content.as_bytes(), &self.encryption_key)?;
287 let encrypted_content = serde_json::to_string_pretty(&encrypted_data)?;
288
289 let temp_path = self.path.with_extension("tmp");
291 fs::write(&temp_path, encrypted_content).await?;
292
293 #[cfg(unix)]
295 {
296 use std::os::unix::fs::PermissionsExt;
297 let mut perms = fs::metadata(&temp_path).await?.permissions();
298 perms.set_mode(self.file_permissions); fs::set_permissions(&temp_path, perms).await?;
300 }
301
302 fs::rename(&temp_path, &self.path).await?;
304
305 debug!("Saved {} keys to encrypted file storage", keys.len());
306 Ok(())
307 }
308
309 pub async fn create_backup(&self) -> Result<PathBuf, StorageError> {
311 if !self.path.exists() {
312 return Err(StorageError::General(
313 "Storage file does not exist".to_string(),
314 ));
315 }
316
317 let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S_%3f");
318 let backup_path = self
319 .path
320 .with_extension(format!("backup_{}.enc", timestamp));
321
322 fs::copy(&self.path, &backup_path).await?;
324
325 #[cfg(unix)]
326 {
327 use std::os::unix::fs::PermissionsExt;
328 let mut perms = fs::metadata(&backup_path).await?.permissions();
329 perms.set_mode(self.file_permissions);
330 fs::set_permissions(&backup_path, perms).await?;
331 }
332
333 debug!("Created secure backup: {}", backup_path.display());
334 Ok(backup_path)
335 }
336
337 pub async fn restore_from_backup(&self, backup_path: &PathBuf) -> Result<(), StorageError> {
339 if !backup_path.exists() {
340 return Err(StorageError::General(
341 "Backup file does not exist".to_string(),
342 ));
343 }
344
345 Self::verify_file_ownership(backup_path).await?;
347
348 let temp_path = self.path.with_extension("restore_tmp");
350 fs::copy(backup_path, &temp_path).await?;
351
352 #[cfg(unix)]
353 {
354 use std::os::unix::fs::PermissionsExt;
355 let mut perms = fs::metadata(&temp_path).await?.permissions();
356 perms.set_mode(self.file_permissions);
357 fs::set_permissions(&temp_path, perms).await?;
358 }
359
360 fs::rename(&temp_path, &self.path).await?;
362
363 info!("Restored from backup: {}", backup_path.display());
364 Ok(())
365 }
366
367 pub async fn cleanup_backups(&self, keep_count: usize) -> Result<(), StorageError> {
369 if let Some(parent) = self.path.parent() {
370 let filename_stem = self
371 .path
372 .file_stem()
373 .and_then(|s| s.to_str())
374 .unwrap_or("keys");
375
376 let mut backups = Vec::new();
377 let mut entries = fs::read_dir(parent).await?;
378
379 while let Some(entry) = entries.next_entry().await? {
380 let path = entry.path();
381 if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
382 if filename.starts_with(&format!("{}.backup_", filename_stem)) {
383 if let Ok(metadata) = entry.metadata().await {
384 backups.push((
385 path,
386 metadata
387 .modified()
388 .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
389 ));
390 }
391 }
392 }
393 }
394
395 backups.sort_by(|a, b| b.1.cmp(&a.1));
397
398 for (backup_path, _) in backups.iter().skip(keep_count) {
400 if let Err(e) = fs::remove_file(backup_path).await {
401 warn!(
402 "Failed to remove old backup {}: {}",
403 backup_path.display(),
404 e
405 );
406 } else {
407 debug!("Removed old backup: {}", backup_path.display());
408 }
409 }
410 }
411
412 Ok(())
413 }
414
415 #[cfg(target_os = "linux")]
417 pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
418 if !self.enable_filesystem_monitoring {
419 return Ok(());
420 }
421
422 use inotify::{Inotify, WatchMask};
423
424 let mut inotify = Inotify::init()
425 .map_err(|e| StorageError::General(format!("Failed to initialize inotify: {}", e)))?;
426
427 if let Some(parent) = self.path.parent() {
429 inotify
430 .watches()
431 .add(
432 parent,
433 WatchMask::MODIFY | WatchMask::ATTRIB | WatchMask::MOVED_TO | WatchMask::DELETE,
434 )
435 .map_err(|e| {
436 StorageError::General(format!("Failed to add inotify watch: {}", e))
437 })?;
438
439 info!("Started filesystem monitoring for: {}", parent.display());
440
441 let path = self.path.clone();
443 let file_permissions = self.file_permissions;
444
445 tokio::spawn(async move {
446 use tracing::{error, warn};
447 let mut buffer = [0; 1024];
448 loop {
449 match inotify.read_events_blocking(&mut buffer) {
450 Ok(events) => {
451 for event in events {
452 if let Some(name) = event.name {
453 if name.to_string_lossy().contains("keys") {
454 warn!(
455 "Detected unauthorized change to auth storage: {:?} (mask: {:?})",
456 name, event.mask
457 );
458
459 if path.exists() {
461 #[cfg(unix)]
462 {
463 use std::os::unix::fs::PermissionsExt;
464 if let Ok(metadata) = std::fs::metadata(&path) {
465 let mode =
466 metadata.permissions().mode() & 0o777;
467 if mode != file_permissions {
468 error!(
469 "Security violation: File permissions changed from {:o} to {:o}",
470 file_permissions, mode
471 );
472 }
473 }
474 }
475 }
476 }
477 }
478 }
479 }
480 Err(e) => {
481 error!("Error reading inotify events: {}", e);
482 break;
483 }
484 }
485 }
486 });
487 }
488
489 Ok(())
490 }
491
492 #[cfg(not(target_os = "linux"))]
494 pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
495 if self.enable_filesystem_monitoring {
496 warn!("Filesystem monitoring is only supported on Linux systems");
497 }
498 Ok(())
499 }
500}
501
502#[async_trait]
503impl StorageBackend for FileStorage {
504 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
505 use crate::crypto::encryption::decrypt_data;
506
507 self.ensure_secure_permissions().await?;
508
509 if !self.path.exists() {
510 return Ok(HashMap::new());
511 }
512
513 let content = fs::read(&self.path).await?;
514 if content.is_empty() {
515 return Ok(HashMap::new());
516 }
517
518 let decrypted_content = if let Ok(encrypted_data) = serde_json::from_slice(&content) {
520 let decrypted_bytes = decrypt_data(&encrypted_data, &self.encryption_key)?;
522 String::from_utf8(decrypted_bytes)
523 .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?
524 } else {
525 let plain_text = String::from_utf8(content)
527 .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?;
528 warn!("Found legacy plain text keys, converting to secure format");
529
530 let legacy_keys: HashMap<String, ApiKey> = serde_json::from_str(&plain_text)?;
532 let secure_keys: HashMap<String, SecureApiKey> = legacy_keys
533 .into_iter()
534 .map(|(id, key)| (id, key.to_secure_storage()))
535 .collect();
536
537 self.save_secure_keys(&secure_keys).await?;
539
540 plain_text
542 };
543
544 let secure_keys: HashMap<String, SecureApiKey> = serde_json::from_str(&decrypted_content)?;
546
547 let keys: HashMap<String, ApiKey> = secure_keys
549 .into_iter()
550 .map(|(id, secure_key)| (id, secure_key.to_api_key()))
551 .collect();
552
553 debug!("Loaded {} keys from encrypted file storage", keys.len());
554 Ok(keys)
555 }
556
557 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
558 let _lock = self.write_mutex.lock().await;
559 let mut keys = self.load_keys().await?;
560 keys.insert(key.id.clone(), key.clone());
561 self.save_all_keys_internal(&keys).await
562 }
563
564 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
565 let _lock = self.write_mutex.lock().await;
566 let mut keys = self.load_keys().await?;
567 keys.remove(key_id);
568 self.save_all_keys_internal(&keys).await
569 }
570
571 async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
572 let _lock = self.write_mutex.lock().await;
573 self.save_all_keys_internal(keys).await
574 }
575}
576
577impl FileStorage {
578 async fn save_all_keys_internal(
579 &self,
580 keys: &HashMap<String, ApiKey>,
581 ) -> Result<(), StorageError> {
582 let secure_keys: HashMap<String, SecureApiKey> = keys
584 .iter()
585 .map(|(id, key)| (id.clone(), key.to_secure_storage()))
586 .collect();
587
588 self.save_secure_keys(&secure_keys).await
589 }
590}
591
592pub struct EnvironmentStorage {
594 var_name: String,
595}
596
597impl EnvironmentStorage {
598 pub fn new(var_name: String) -> Self {
599 Self { var_name }
600 }
601}
602
603#[async_trait]
604impl StorageBackend for EnvironmentStorage {
605 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
606 match std::env::var(&self.var_name) {
607 Ok(content) => {
608 if content.trim().is_empty() {
609 return Ok(HashMap::new());
610 }
611 let keys: HashMap<String, ApiKey> = serde_json::from_str(&content)?;
612 debug!("Loaded {} keys from environment storage", keys.len());
613 Ok(keys)
614 }
615 Err(_) => {
616 debug!(
617 "Environment variable {} not found, returning empty keys",
618 self.var_name
619 );
620 Ok(HashMap::new())
621 }
622 }
623 }
624
625 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
626 let mut keys = self.load_keys().await?;
627 keys.insert(key.id.clone(), key.clone());
628 self.save_all_keys(&keys).await
629 }
630
631 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
632 let mut keys = self.load_keys().await?;
633 keys.remove(key_id);
634 self.save_all_keys(&keys).await
635 }
636
637 async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
638 let content = serde_json::to_string(keys)?;
639 std::env::set_var(&self.var_name, content);
640
641 debug!("Saved {} keys to environment storage", keys.len());
642 Ok(())
643 }
644}
645
646pub struct MemoryStorage {
648 keys: tokio::sync::RwLock<HashMap<String, ApiKey>>,
649}
650
651impl MemoryStorage {
652 pub fn new() -> Self {
653 Self {
654 keys: tokio::sync::RwLock::new(HashMap::new()),
655 }
656 }
657}
658
659#[async_trait]
660impl StorageBackend for MemoryStorage {
661 async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
662 let keys = self.keys.read().await;
663 debug!("Loaded {} keys from memory storage", keys.len());
664 Ok(keys.clone())
665 }
666
667 async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
668 let mut keys = self.keys.write().await;
669 keys.insert(key.id.clone(), key.clone());
670 debug!("Saved key {} to memory storage", key.id);
671 Ok(())
672 }
673
674 async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
675 let mut keys = self.keys.write().await;
676 keys.remove(key_id);
677 debug!("Deleted key {} from memory storage", key_id);
678 Ok(())
679 }
680
681 async fn save_all_keys(&self, new_keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
682 let mut keys = self.keys.write().await;
683 *keys = new_keys.clone();
684 debug!(
685 "Replaced all keys in memory storage with {} keys",
686 new_keys.len()
687 );
688 Ok(())
689 }
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use crate::models::{ApiKey, Role};
696 use chrono::{Duration, Utc};
697 use std::collections::HashMap;
698 use tempfile::TempDir;
699 use tokio::fs;
700
701 fn create_test_key(name: &str, role: Role) -> ApiKey {
703 ApiKey::new(
704 name.to_string(),
705 role,
706 Some(Utc::now() + Duration::days(30)),
707 vec!["127.0.0.1".to_string()],
708 )
709 }
710
711 fn create_test_keys() -> HashMap<String, ApiKey> {
713 let mut keys = HashMap::new();
714
715 let admin_key = create_test_key("admin-key", Role::Admin);
716 let operator_key = create_test_key("operator-key", Role::Operator);
717 let monitor_key = create_test_key("monitor-key", Role::Monitor);
718
719 keys.insert(admin_key.id.clone(), admin_key);
720 keys.insert(operator_key.id.clone(), operator_key);
721 keys.insert(monitor_key.id.clone(), monitor_key);
722
723 keys
724 }
725
726 #[test]
727 fn test_storage_error_display() {
728 let error = StorageError::General("test error".to_string());
729 assert_eq!(error.to_string(), "Storage error: test error");
730
731 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
732 let storage_error = StorageError::Io(io_error);
733 assert!(storage_error.to_string().contains("File I/O error"));
734
735 let perm_error = StorageError::Permission("access denied".to_string());
736 assert_eq!(perm_error.to_string(), "Permission error: access denied");
737 }
738
739 #[test]
740 fn test_storage_error_from_io_error() {
741 let io_error =
742 std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
743 let storage_error: StorageError = io_error.into();
744
745 match storage_error {
746 StorageError::Io(_) => (),
747 _ => panic!("Expected Io variant"),
748 }
749 }
750
751 #[test]
752 fn test_storage_error_from_serde_error() {
753 let serde_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
754 let storage_error: StorageError = serde_error.into();
755
756 match storage_error {
757 StorageError::Serialization(_) => (),
758 _ => panic!("Expected Serialization variant"),
759 }
760 }
761
762 mod memory_storage_tests {
763 use super::*;
764
765 #[tokio::test]
766 async fn test_memory_storage_new() {
767 let storage = MemoryStorage::new();
768 let keys = storage.load_keys().await.unwrap();
769 assert!(keys.is_empty());
770 }
771
772 #[tokio::test]
773 async fn test_memory_storage_save_and_load_key() {
774 let storage = MemoryStorage::new();
775 let test_key = create_test_key("test-key", Role::Operator);
776
777 storage.save_key(&test_key).await.unwrap();
778
779 let keys = storage.load_keys().await.unwrap();
780 assert_eq!(keys.len(), 1);
781 assert!(keys.contains_key(&test_key.id));
782
783 let loaded_key = &keys[&test_key.id];
784 assert_eq!(loaded_key.name, test_key.name);
785 assert_eq!(loaded_key.role, test_key.role);
786 }
787
788 #[tokio::test]
789 async fn test_memory_storage_save_multiple_keys() {
790 let storage = MemoryStorage::new();
791 let test_keys = create_test_keys();
792
793 for key in test_keys.values() {
794 storage.save_key(key).await.unwrap();
795 }
796
797 let loaded_keys = storage.load_keys().await.unwrap();
798 assert_eq!(loaded_keys.len(), test_keys.len());
799
800 for (id, key) in test_keys.iter() {
801 assert!(loaded_keys.contains_key(id));
802 assert_eq!(loaded_keys[id].name, key.name);
803 }
804 }
805
806 #[tokio::test]
807 async fn test_memory_storage_delete_key() {
808 let storage = MemoryStorage::new();
809 let test_key = create_test_key("test-key", Role::Monitor);
810
811 storage.save_key(&test_key).await.unwrap();
812 assert_eq!(storage.load_keys().await.unwrap().len(), 1);
813
814 storage.delete_key(&test_key.id).await.unwrap();
815 let keys = storage.load_keys().await.unwrap();
816 assert!(keys.is_empty());
817 }
818
819 #[tokio::test]
820 async fn test_memory_storage_delete_nonexistent_key() {
821 let storage = MemoryStorage::new();
822
823 storage.delete_key("nonexistent").await.unwrap();
825 assert!(storage.load_keys().await.unwrap().is_empty());
826 }
827
828 #[tokio::test]
829 async fn test_memory_storage_save_all_keys() {
830 let storage = MemoryStorage::new();
831 let test_keys = create_test_keys();
832
833 storage.save_all_keys(&test_keys).await.unwrap();
834
835 let loaded_keys = storage.load_keys().await.unwrap();
836 assert_eq!(loaded_keys.len(), test_keys.len());
837
838 for (id, key) in test_keys.iter() {
839 assert!(loaded_keys.contains_key(id));
840 assert_eq!(loaded_keys[id].name, key.name);
841 }
842 }
843
844 #[tokio::test]
845 async fn test_memory_storage_save_all_keys_replaces_existing() {
846 let storage = MemoryStorage::new();
847
848 let initial_keys = create_test_keys();
850 storage.save_all_keys(&initial_keys).await.unwrap();
851 assert_eq!(storage.load_keys().await.unwrap().len(), initial_keys.len());
852
853 let mut new_keys = HashMap::new();
855 let new_key = create_test_key("new-key", Role::Admin);
856 new_keys.insert(new_key.id.clone(), new_key);
857
858 storage.save_all_keys(&new_keys).await.unwrap();
859
860 let loaded_keys = storage.load_keys().await.unwrap();
861 assert_eq!(loaded_keys.len(), 1);
862 assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
863 }
864
865 #[tokio::test]
866 async fn test_memory_storage_concurrent_access() {
867 let storage = std::sync::Arc::new(MemoryStorage::new());
868 let mut handles = vec![];
869
870 for i in 0..10 {
872 let storage_clone = storage.clone();
873 let handle = tokio::spawn(async move {
874 let key = create_test_key(&format!("key-{}", i), Role::Operator);
875 storage_clone.save_key(&key).await.unwrap();
876 key.id
877 });
878 handles.push(handle);
879 }
880
881 let mut saved_ids = vec![];
882 for handle in handles {
883 saved_ids.push(handle.await.unwrap());
884 }
885
886 let keys = storage.load_keys().await.unwrap();
887 assert_eq!(keys.len(), 10);
888
889 for id in saved_ids {
890 assert!(keys.contains_key(&id));
891 }
892 }
893 }
894
895 mod environment_storage_tests {
896 use super::*;
897
898 #[tokio::test]
899 async fn test_environment_storage_new() {
900 let storage = EnvironmentStorage::new("TEST_MCP_KEYS".to_string());
901
902 std::env::remove_var("TEST_MCP_KEYS");
904
905 let keys = storage.load_keys().await.unwrap();
906 assert!(keys.is_empty());
907 }
908
909 #[tokio::test]
910 async fn test_environment_storage_save_and_load_key() {
911 let var_name = "TEST_MCP_KEYS_SAVE_LOAD";
912 std::env::remove_var(var_name);
913
914 let storage = EnvironmentStorage::new(var_name.to_string());
915 let test_key = create_test_key("env-test-key", Role::Monitor);
916
917 storage.save_key(&test_key).await.unwrap();
918
919 let keys = storage.load_keys().await.unwrap();
920 assert_eq!(keys.len(), 1);
921 assert!(keys.contains_key(&test_key.id));
922
923 assert!(std::env::var(var_name).is_ok());
925
926 std::env::remove_var(var_name);
928 }
929
930 #[tokio::test]
931 async fn test_environment_storage_multiple_keys() {
932 let var_name = "TEST_MCP_KEYS_MULTIPLE";
933 std::env::remove_var(var_name);
934
935 let storage = EnvironmentStorage::new(var_name.to_string());
936 let test_keys = create_test_keys();
937
938 storage.save_all_keys(&test_keys).await.unwrap();
939
940 let loaded_keys = storage.load_keys().await.unwrap();
941 assert_eq!(loaded_keys.len(), test_keys.len());
942
943 for (id, key) in test_keys.iter() {
944 assert!(loaded_keys.contains_key(id));
945 assert_eq!(loaded_keys[id].name, key.name);
946 }
947
948 std::env::remove_var(var_name);
950 }
951
952 #[tokio::test]
953 async fn test_environment_storage_delete_key() {
954 let var_name = "TEST_MCP_KEYS_DELETE";
955 std::env::remove_var(var_name);
956
957 let storage = EnvironmentStorage::new(var_name.to_string());
958 let test_keys = create_test_keys();
959 let key_to_delete = test_keys.values().next().unwrap().id.clone();
960
961 storage.save_all_keys(&test_keys).await.unwrap();
962 assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
963
964 storage.delete_key(&key_to_delete).await.unwrap();
965
966 let remaining_keys = storage.load_keys().await.unwrap();
967 assert_eq!(remaining_keys.len(), test_keys.len() - 1);
968 assert!(!remaining_keys.contains_key(&key_to_delete));
969
970 std::env::remove_var(var_name);
972 }
973
974 #[tokio::test]
975 async fn test_environment_storage_empty_content() {
976 let var_name = "TEST_MCP_KEYS_EMPTY";
977 std::env::set_var(var_name, "");
978
979 let storage = EnvironmentStorage::new(var_name.to_string());
980 let keys = storage.load_keys().await.unwrap();
981 assert!(keys.is_empty());
982
983 std::env::remove_var(var_name);
985 }
986
987 #[tokio::test]
988 async fn test_environment_storage_invalid_json() {
989 let var_name = "TEST_MCP_KEYS_INVALID";
990 std::env::set_var(var_name, "invalid json content");
991
992 let storage = EnvironmentStorage::new(var_name.to_string());
993 let result = storage.load_keys().await;
994
995 assert!(result.is_err());
996 match result.unwrap_err() {
997 StorageError::Serialization(_) => (),
998 _ => panic!("Expected serialization error"),
999 }
1000
1001 std::env::remove_var(var_name);
1003 }
1004
1005 #[tokio::test]
1006 async fn test_environment_storage_overwrite_existing() {
1007 let var_name = "TEST_MCP_KEYS_OVERWRITE";
1008 std::env::remove_var(var_name);
1009
1010 let storage = EnvironmentStorage::new(var_name.to_string());
1011
1012 let initial_keys = create_test_keys();
1014 storage.save_all_keys(&initial_keys).await.unwrap();
1015
1016 let mut new_keys = HashMap::new();
1018 let new_key = create_test_key("overwrite-key", Role::Admin);
1019 new_keys.insert(new_key.id.clone(), new_key);
1020
1021 storage.save_all_keys(&new_keys).await.unwrap();
1022
1023 let loaded_keys = storage.load_keys().await.unwrap();
1024 assert_eq!(loaded_keys.len(), 1);
1025 assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
1026
1027 std::env::remove_var(var_name);
1029 }
1030 }
1031
1032 mod file_storage_tests {
1033 use super::*;
1034
1035 async fn create_test_file_storage() -> (FileStorage, TempDir) {
1036 std::env::set_var(
1038 "PULSEENGINE_MCP_MASTER_KEY",
1039 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1040 );
1041 let temp_dir = TempDir::new().unwrap();
1042 let storage_path = temp_dir.path().join("test_keys.enc");
1043
1044 let storage = FileStorage::new(
1045 storage_path,
1046 0o600,
1047 0o700,
1048 false, false, )
1051 .await
1052 .unwrap();
1053
1054 (storage, temp_dir)
1055 }
1056
1057 #[tokio::test]
1058 async fn test_file_storage_new() {
1059 let (storage, _temp_dir) = create_test_file_storage().await;
1060
1061 let keys = storage.load_keys().await.unwrap();
1063 assert!(keys.is_empty());
1064
1065 assert!(storage.path.exists());
1067 }
1068
1069 #[tokio::test]
1070 async fn test_file_storage_save_and_load_key() {
1071 let (storage, _temp_dir) = create_test_file_storage().await;
1072 let test_key = create_test_key("file-test-key", Role::Operator);
1073
1074 storage.save_key(&test_key).await.unwrap();
1075
1076 let keys = storage.load_keys().await.unwrap();
1077 assert_eq!(keys.len(), 1);
1078 assert!(keys.contains_key(&test_key.id));
1079
1080 let loaded_key = &keys[&test_key.id];
1081 assert_eq!(loaded_key.name, test_key.name);
1082 assert_eq!(loaded_key.role, test_key.role);
1083 assert_eq!(loaded_key.key, "***redacted***");
1085 }
1086
1087 #[tokio::test]
1088 async fn test_file_storage_multiple_keys() {
1089 let (storage, _temp_dir) = create_test_file_storage().await;
1090 let test_keys = create_test_keys();
1091
1092 storage.save_all_keys(&test_keys).await.unwrap();
1093
1094 let loaded_keys = storage.load_keys().await.unwrap();
1095 assert_eq!(loaded_keys.len(), test_keys.len());
1096
1097 for (id, key) in test_keys.iter() {
1098 assert!(loaded_keys.contains_key(id));
1099 assert_eq!(loaded_keys[id].name, key.name);
1100 assert_eq!(loaded_keys[id].role, key.role);
1101 }
1102 }
1103
1104 #[tokio::test]
1105 async fn test_file_storage_delete_key() {
1106 let (storage, _temp_dir) = create_test_file_storage().await;
1107 let test_keys = create_test_keys();
1108 let key_to_delete = test_keys.values().next().unwrap().id.clone();
1109
1110 storage.save_all_keys(&test_keys).await.unwrap();
1111 assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
1112
1113 storage.delete_key(&key_to_delete).await.unwrap();
1114
1115 let remaining_keys = storage.load_keys().await.unwrap();
1116 assert_eq!(remaining_keys.len(), test_keys.len() - 1);
1117 assert!(!remaining_keys.contains_key(&key_to_delete));
1118 }
1119
1120 #[tokio::test]
1121 async fn test_file_storage_encryption() {
1122 let (storage, _temp_dir) = create_test_file_storage().await;
1123 let test_key = create_test_key("encryption-test", Role::Admin);
1124
1125 storage.save_key(&test_key).await.unwrap();
1126
1127 let raw_content = fs::read(&storage.path).await.unwrap();
1129 let raw_text = String::from_utf8_lossy(&raw_content);
1130
1131 assert!(!raw_text.contains(&test_key.name));
1133 assert!(!raw_text.contains(&test_key.key));
1134
1135 let loaded_keys = storage.load_keys().await.unwrap();
1137 assert_eq!(loaded_keys.len(), 1);
1138 assert!(loaded_keys.contains_key(&test_key.id));
1139 }
1140
1141 #[tokio::test]
1142 async fn test_file_storage_empty_file() {
1143 let temp_dir = TempDir::new().unwrap();
1144 let storage_path = temp_dir.path().join("empty_keys.enc");
1145
1146 fs::write(&storage_path, "").await.unwrap();
1148
1149 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1150 .await
1151 .unwrap();
1152
1153 let keys = storage.load_keys().await.unwrap();
1154 assert!(keys.is_empty());
1155 }
1156
1157 #[tokio::test]
1158 async fn test_file_storage_nonexistent_file() {
1159 let temp_dir = TempDir::new().unwrap();
1160 let storage_path = temp_dir.path().join("nonexistent").join("keys.enc");
1161
1162 let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1164 .await
1165 .unwrap();
1166
1167 let keys = storage.load_keys().await.unwrap();
1169 assert!(keys.is_empty());
1170 assert!(storage_path.exists());
1171 }
1172
1173 #[tokio::test]
1174 #[allow(clippy::await_holding_lock)] async fn test_file_storage_persistence() {
1176 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1179 let _lock = TEST_LOCK.lock().await;
1181
1182 let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1184 std::env::set_var(
1185 "PULSEENGINE_MCP_MASTER_KEY",
1186 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1187 );
1188
1189 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1191
1192 let temp_dir = TempDir::new().unwrap();
1193 let storage_path = temp_dir.path().join("persistent_keys.enc");
1194 let test_keys = create_test_keys();
1195
1196 {
1198 let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1199 .await
1200 .unwrap();
1201
1202 storage.save_all_keys(&test_keys).await.unwrap();
1203 }
1204
1205 assert!(storage_path.exists());
1207 let file_metadata = std::fs::metadata(&storage_path).unwrap();
1208 assert!(file_metadata.len() > 0);
1209
1210 {
1212 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1213 .await
1214 .unwrap();
1215
1216 let loaded_keys = storage.load_keys().await.unwrap();
1217 assert_eq!(loaded_keys.len(), test_keys.len());
1218
1219 for (id, key) in test_keys.iter() {
1220 assert!(loaded_keys.contains_key(id));
1221 assert_eq!(loaded_keys[id].name, key.name);
1222 }
1223 }
1224
1225 match original_master_key {
1227 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1228 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1229 }
1230 }
1231
1232 #[tokio::test]
1233 async fn test_file_storage_backup_and_restore() {
1234 let (storage, _temp_dir) = create_test_file_storage().await;
1235 let test_keys = create_test_keys();
1236
1237 storage.save_all_keys(&test_keys).await.unwrap();
1239
1240 let backup_path = storage.create_backup().await.unwrap();
1242 assert!(backup_path.exists());
1243 assert!(backup_path.to_string_lossy().contains("backup_"));
1244
1245 let mut modified_keys = HashMap::new();
1247 let new_key = create_test_key("backup-test", Role::Monitor);
1248 modified_keys.insert(new_key.id.clone(), new_key);
1249 storage.save_all_keys(&modified_keys).await.unwrap();
1250
1251 assert_eq!(storage.load_keys().await.unwrap().len(), 1);
1253
1254 storage.restore_from_backup(&backup_path).await.unwrap();
1256
1257 let restored_keys = storage.load_keys().await.unwrap();
1259 assert_eq!(restored_keys.len(), test_keys.len());
1260
1261 for id in test_keys.keys() {
1262 assert!(restored_keys.contains_key(id));
1263 }
1264 }
1265
1266 #[tokio::test]
1267 async fn test_file_storage_backup_nonexistent_storage() {
1268 let temp_dir = TempDir::new().unwrap();
1269 let storage_path = temp_dir.path().join("missing_keys.enc");
1270
1271 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1272 .await
1273 .unwrap();
1274
1275 fs::remove_file(&storage.path).await.unwrap();
1277
1278 let result = storage.create_backup().await;
1279 assert!(result.is_err());
1280 match result.unwrap_err() {
1281 StorageError::General(msg) => assert!(msg.contains("does not exist")),
1282 _ => panic!("Expected general error"),
1283 }
1284 }
1285
1286 #[tokio::test]
1287 async fn test_file_storage_restore_nonexistent_backup() {
1288 let (storage, temp_dir) = create_test_file_storage().await;
1289 let nonexistent_backup = temp_dir.path().join("nonexistent_backup.enc");
1290
1291 let result = storage.restore_from_backup(&nonexistent_backup).await;
1292 assert!(result.is_err());
1293 match result.unwrap_err() {
1294 StorageError::General(msg) => assert!(msg.contains("does not exist")),
1295 _ => panic!("Expected general error"),
1296 }
1297 }
1298
1299 #[tokio::test]
1300 #[allow(clippy::await_holding_lock)] async fn test_file_storage_cleanup_backups() {
1302 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1304 let _lock = TEST_LOCK.lock().await;
1305 let original_master_key = {
1306 let original = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1308
1309 std::env::set_var(
1311 "PULSEENGINE_MCP_MASTER_KEY",
1312 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1313 );
1314 original
1315 };
1316
1317 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1319
1320 let (storage, _temp_dir) = create_test_file_storage().await;
1321 let test_key = create_test_key("cleanup-test", Role::Admin);
1322
1323 storage.save_key(&test_key).await.unwrap();
1324
1325 let mut backup_paths = vec![];
1327 for _i in 0..5 {
1328 let backup_path = storage.create_backup().await.unwrap();
1329 backup_paths.push(backup_path);
1330 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1332 }
1333
1334 for (i, path) in backup_paths.iter().enumerate() {
1336 assert!(path.exists(), "Backup {} does not exist: {:?}", i, path);
1337 }
1338
1339 storage.cleanup_backups(2).await.unwrap();
1341
1342 let parent = storage.path.parent().unwrap();
1344 let mut remaining_backups = 0;
1345 let mut entries = fs::read_dir(parent).await.unwrap();
1346
1347 while let Some(entry) = entries.next_entry().await.unwrap() {
1348 if entry.file_name().to_string_lossy().contains("backup_") {
1349 remaining_backups += 1;
1350 }
1351 }
1352
1353 assert_eq!(remaining_backups, 2);
1354
1355 match original_master_key {
1357 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1358 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1359 }
1360 }
1361
1362 #[cfg(unix)]
1363 #[tokio::test]
1364 async fn test_file_storage_permissions() {
1365 use std::os::unix::fs::PermissionsExt;
1366
1367 let (storage, _temp_dir) = create_test_file_storage().await;
1368 let test_key = create_test_key("perm-test", Role::Operator);
1369
1370 storage.save_key(&test_key).await.unwrap();
1371
1372 let metadata = fs::metadata(&storage.path).await.unwrap();
1374 let mode = metadata.permissions().mode() & 0o777;
1375 assert_eq!(mode, 0o600);
1376
1377 if let Some(parent) = storage.path.parent() {
1379 let parent_metadata = fs::metadata(parent).await.unwrap();
1380 let parent_mode = parent_metadata.permissions().mode() & 0o777;
1381 assert_eq!(parent_mode, 0o700);
1382 }
1383 }
1384
1385 #[tokio::test]
1386 #[allow(clippy::await_holding_lock)] async fn test_file_storage_atomic_operations() {
1388 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1390 let _lock = TEST_LOCK.lock().await;
1391
1392 let original_master_key = {
1393 let original = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1395
1396 std::env::set_var(
1398 "PULSEENGINE_MCP_MASTER_KEY",
1399 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1400 );
1401 original
1402 };
1403
1404 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1406
1407 let (storage, _temp_dir) = create_test_file_storage().await;
1408 let initial_keys = create_test_keys();
1409
1410 storage.save_all_keys(&initial_keys).await.unwrap();
1411
1412 let storage_clone = std::sync::Arc::new(storage);
1414 let mut handles = vec![];
1415
1416 for i in 0..5 {
1418 let storage_ref = storage_clone.clone();
1419 let handle = tokio::spawn(async move {
1420 let key = create_test_key(&format!("concurrent-{}", i), Role::Monitor);
1421 tokio::time::timeout(
1423 std::time::Duration::from_secs(10),
1424 storage_ref.save_key(&key),
1425 )
1426 .await
1427 });
1428 handles.push(handle);
1429 }
1430
1431 for handle in handles {
1433 match tokio::time::timeout(std::time::Duration::from_secs(15), handle).await {
1435 Ok(result) => {
1436 match result {
1438 Ok(save_result) => {
1439 let _ = save_result;
1441 }
1442 Err(_) => {
1443 }
1445 }
1446 }
1447 Err(_) => {
1448 eprintln!(
1450 "Concurrent operation timed out - acceptable on slower platforms"
1451 );
1452 }
1453 }
1454 }
1455
1456 let final_keys = storage_clone.load_keys().await.unwrap();
1458 assert!(final_keys.len() >= initial_keys.len());
1459
1460 for id in initial_keys.keys() {
1462 assert!(final_keys.contains_key(id));
1463 }
1464
1465 match original_master_key {
1467 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1468 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1469 }
1470 }
1471 }
1472
1473 mod storage_factory_tests {
1474 use super::*;
1475 use crate::config::StorageConfig;
1476
1477 #[tokio::test]
1478 async fn test_create_memory_storage_backend() {
1479 let config = StorageConfig::Memory;
1480 let backend = create_storage_backend(&config).await.unwrap();
1481
1482 let test_key = create_test_key("memory-factory-test", Role::Admin);
1484 backend.save_key(&test_key).await.unwrap();
1485
1486 let keys = backend.load_keys().await.unwrap();
1487 assert_eq!(keys.len(), 1);
1488 assert!(keys.contains_key(&test_key.id));
1489 }
1490
1491 #[tokio::test]
1492 async fn test_create_environment_storage_backend() {
1493 let var_name = "TEST_FACTORY_ENV_STORAGE";
1494 std::env::remove_var(var_name);
1495
1496 let config = StorageConfig::Environment {
1497 prefix: var_name.to_string(),
1498 };
1499 let backend = create_storage_backend(&config).await.unwrap();
1500
1501 let test_key = create_test_key("env-factory-test", Role::Operator);
1503 backend.save_key(&test_key).await.unwrap();
1504
1505 let keys = backend.load_keys().await.unwrap();
1506 assert_eq!(keys.len(), 1);
1507 assert!(keys.contains_key(&test_key.id));
1508
1509 std::env::remove_var(var_name);
1511 }
1512
1513 #[tokio::test]
1514 async fn test_create_file_storage_backend() {
1515 let temp_dir = TempDir::new().unwrap();
1516 let storage_path = temp_dir.path().join("factory_test_keys.enc");
1517
1518 let config = StorageConfig::File {
1519 path: storage_path.clone(),
1520 file_permissions: 0o600,
1521 dir_permissions: 0o700,
1522 require_secure_filesystem: false,
1523 enable_filesystem_monitoring: false,
1524 };
1525 let backend = create_storage_backend(&config).await.unwrap();
1526
1527 let test_key = create_test_key("file-factory-test", Role::Monitor);
1529 backend.save_key(&test_key).await.unwrap();
1530
1531 let keys = backend.load_keys().await.unwrap();
1532 assert_eq!(keys.len(), 1);
1533 assert!(keys.contains_key(&test_key.id));
1534
1535 assert!(storage_path.exists());
1537 }
1538
1539 #[tokio::test]
1540 async fn test_create_file_storage_backend_with_nested_path() {
1541 let temp_dir = TempDir::new().unwrap();
1542 let storage_path = temp_dir.path().join("nested").join("dirs").join("keys.enc");
1543
1544 let config = StorageConfig::File {
1545 path: storage_path.clone(),
1546 file_permissions: 0o600,
1547 dir_permissions: 0o700,
1548 require_secure_filesystem: false,
1549 enable_filesystem_monitoring: false,
1550 };
1551 let backend = create_storage_backend(&config).await.unwrap();
1552
1553 assert!(storage_path.parent().unwrap().exists());
1555
1556 let test_key = create_test_key(
1558 "nested-factory-test",
1559 Role::Device {
1560 allowed_devices: vec!["device1".to_string()],
1561 },
1562 );
1563 backend.save_key(&test_key).await.unwrap();
1564
1565 let keys = backend.load_keys().await.unwrap();
1566 assert_eq!(keys.len(), 1);
1567 assert!(keys.contains_key(&test_key.id));
1568 }
1569 }
1570
1571 #[tokio::test]
1572 async fn test_storage_backend_trait_object() {
1573 let memory_storage: Box<dyn StorageBackend> = Box::new(MemoryStorage::new());
1575 let env_storage: Box<dyn StorageBackend> =
1576 Box::new(EnvironmentStorage::new("TEST_TRAIT_OBJECT".to_string()));
1577
1578 let storages: Vec<Box<dyn StorageBackend>> = vec![memory_storage, env_storage];
1579
1580 for (i, storage) in storages.into_iter().enumerate() {
1581 let test_key = create_test_key(
1582 &format!("trait-test-{}", i),
1583 Role::Custom {
1584 permissions: vec!["test:read".to_string()],
1585 },
1586 );
1587
1588 storage.save_key(&test_key).await.unwrap();
1589 let keys = storage.load_keys().await.unwrap();
1590 assert_eq!(keys.len(), 1);
1591 assert!(keys.contains_key(&test_key.id));
1592 }
1593
1594 std::env::remove_var("TEST_TRAIT_OBJECT");
1596 }
1597
1598 #[tokio::test]
1599 async fn test_secure_api_key_conversion() {
1600 let original_key = create_test_key("conversion-test", Role::Admin);
1601 let secure_key = original_key.to_secure_storage();
1602 let restored_key = secure_key.to_api_key();
1603
1604 assert_eq!(restored_key.id, original_key.id);
1606 assert_eq!(restored_key.name, original_key.name);
1607 assert_eq!(restored_key.role, original_key.role);
1608 assert_eq!(restored_key.created_at, original_key.created_at);
1609 assert_eq!(restored_key.expires_at, original_key.expires_at);
1610 assert_eq!(restored_key.ip_whitelist, original_key.ip_whitelist);
1611 assert_eq!(restored_key.active, original_key.active);
1612 assert_eq!(restored_key.usage_count, original_key.usage_count);
1613
1614 assert_eq!(restored_key.key, "***redacted***");
1616 assert_ne!(restored_key.key, original_key.key);
1617
1618 assert_eq!(restored_key.secret_hash, original_key.secret_hash);
1620 assert_eq!(restored_key.salt, original_key.salt);
1621 }
1622}