1use crate::storage::{EncryptedFilesystemStorage, StorageBackend, StorageError};
2use chrono::{DateTime, Utc};
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct MachineToken {
11 pub machine_token: String,
13 pub expires_at: String,
15 pub gateway_id: String,
17 #[serde(default)]
19 pub abilities: Vec<String>,
20 pub issued_at: String,
22}
23
24impl MachineToken {
25 pub fn new(
27 machine_token: String,
28 expires_at: String,
29 gateway_id: String,
30 abilities: Vec<String>,
31 ) -> Self {
32 let issued_at = Utc::now().to_rfc3339();
33
34 Self {
35 machine_token,
36 expires_at,
37 gateway_id,
38 abilities,
39 issued_at,
40 }
41 }
42
43 pub fn is_expired(&self) -> bool {
45 match DateTime::parse_from_rfc3339(&self.expires_at) {
47 Ok(expiry) => {
48 let now = Utc::now();
49 expiry.with_timezone(&Utc) < now
50 }
51 Err(e) => {
52 tracing::warn!("Failed to parse token expiry date: {}", e);
53 true
55 }
56 }
57 }
58
59 pub fn is_valid(&self) -> bool {
61 !self.is_expired()
62 }
63}
64
65pub async fn save_token<T>(
118 instance_id: &str,
119 token_type: &str,
120 token: &T,
121) -> Result<(), StorageError>
122where
123 T: Serialize,
124{
125 let token_path = format!("runbeam/{}.json", token_type);
126 tracing::debug!(
127 "Saving token: type={}, instance={}, path={}",
128 token_type,
129 instance_id,
130 token_path
131 );
132
133 let storage = EncryptedFilesystemStorage::new_with_instance(instance_id)
135 .await
136 .map_err(|e| {
137 tracing::error!("Failed to initialize encrypted storage: {}", e);
138 e
139 })?;
140
141 let json = serde_json::to_vec_pretty(&token).map_err(|e| {
143 tracing::error!("Failed to serialize token: {}", e);
144 StorageError::Config(format!("JSON serialization failed: {}", e))
145 })?;
146
147 storage.write_file_str(&token_path, &json).await?;
149
150 tracing::info!(
151 "Token saved successfully to encrypted storage: type={}, instance={}",
152 token_type,
153 instance_id
154 );
155
156 Ok(())
157}
158
159pub async fn load_token<T>(instance_id: &str, token_type: &str) -> Result<Option<T>, StorageError>
193where
194 T: DeserializeOwned,
195{
196 let token_path = format!("runbeam/{}.json", token_type);
197 tracing::debug!(
198 "Loading token: type={}, instance={}, path={}",
199 token_type,
200 instance_id,
201 token_path
202 );
203
204 let storage = EncryptedFilesystemStorage::new_with_instance(instance_id)
206 .await
207 .map_err(|e| {
208 tracing::debug!("Failed to initialize encrypted storage: {}", e);
209 e
210 })?;
211
212 if !storage.exists_str(&token_path) {
214 tracing::debug!("No token file found: type={}", token_type);
215 return Ok(None);
216 }
217
218 tracing::debug!("Token found in encrypted filesystem, loading...");
220 let json = storage.read_file_str(&token_path).await?;
221
222 let token: T = serde_json::from_slice(&json).map_err(|e| {
224 tracing::error!("Failed to deserialize token: {}", e);
225 StorageError::Config(format!("JSON deserialization failed: {}", e))
226 })?;
227
228 tracing::debug!(
229 "Token loaded successfully from encrypted filesystem: type={}",
230 token_type
231 );
232 Ok(Some(token))
233}
234
235pub async fn clear_token(instance_id: &str, token_type: &str) -> Result<(), StorageError> {
264 let token_path = format!("runbeam/{}.json", token_type);
265 tracing::debug!(
266 "Clearing token: type={}, instance={}, path={}",
267 token_type,
268 instance_id,
269 token_path
270 );
271
272 let storage = EncryptedFilesystemStorage::new_with_instance(instance_id)
274 .await
275 .map_err(|e| {
276 tracing::debug!("Failed to initialize encrypted storage: {}", e);
277 e
278 })?;
279
280 if !storage.exists_str(&token_path) {
282 tracing::debug!("No token file to clear: type={}", token_type);
283 return Ok(());
284 }
285
286 tracing::debug!("Clearing token from encrypted filesystem storage");
288 storage.remove_str(&token_path).await.map_err(|e| {
289 tracing::error!("Failed to clear token: {}", e);
290 e
291 })?;
292
293 tracing::info!("Token cleared successfully: type={}", token_type);
294 Ok(())
295}
296
297pub async fn save_token_with_key(
316 instance_id: &str,
317 token: &MachineToken,
318 encryption_key: &str,
319) -> Result<(), StorageError> {
320 let token_path = "runbeam/auth.json";
321 tracing::debug!(
322 "Saving machine token with explicit encryption key: gateway={}, instance={}",
323 token.gateway_id,
324 instance_id
325 );
326
327 let storage =
329 EncryptedFilesystemStorage::new_with_instance_and_key(instance_id, encryption_key).await?;
330
331 let json = serde_json::to_vec_pretty(&token).map_err(|e| {
333 tracing::error!("Failed to serialize machine token: {}", e);
334 StorageError::Config(format!("JSON serialization failed: {}", e))
335 })?;
336
337 storage.write_file_str(token_path, &json).await?;
339
340 tracing::info!(
341 "Machine token saved successfully with explicit key: gateway_id={}, expires_at={}",
342 token.gateway_id,
343 token.expires_at
344 );
345
346 Ok(())
347}
348
349pub async fn save_machine_token(
371 instance_id: &str,
372 token: &MachineToken,
373) -> Result<(), StorageError> {
374 save_token(instance_id, "auth", token).await
375}
376
377pub async fn load_machine_token(instance_id: &str) -> Result<Option<MachineToken>, StorageError> {
393 load_token(instance_id, "auth").await
394}
395
396pub async fn clear_machine_token(instance_id: &str) -> Result<(), StorageError> {
411 clear_token(instance_id, "auth").await
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use serial_test::serial;
418
419 fn setup_test_encryption() -> impl Drop {
421 use base64::Engine;
422 use secrecy::ExposeSecret;
423 use std::env;
424
425 let identity = age::x25519::Identity::generate();
426 let key_base64 = base64::engine::general_purpose::STANDARD
427 .encode(identity.to_string().expose_secret().as_bytes());
428 env::set_var("RUNBEAM_ENCRYPTION_KEY", &key_base64);
429
430 struct Guard;
432 impl Drop for Guard {
433 fn drop(&mut self) {
434 std::env::remove_var("RUNBEAM_ENCRYPTION_KEY");
435 }
436 }
437 Guard
438 }
439
440 #[test]
441 fn test_machine_token_creation() {
442 let token = MachineToken::new(
443 "test_token".to_string(),
444 "2025-12-31T23:59:59Z".to_string(),
445 "gw123".to_string(),
446 vec!["harmony:send".to_string(), "harmony:receive".to_string()],
447 );
448
449 assert_eq!(token.machine_token, "test_token");
450 assert_eq!(token.gateway_id, "gw123");
451 assert_eq!(token.abilities.len(), 2);
452 assert!(!token.issued_at.is_empty());
453 }
454
455 #[test]
456 fn test_machine_token_is_expired() {
457 let expired_token = MachineToken::new(
459 "test_token".to_string(),
460 "2020-01-01T00:00:00Z".to_string(),
461 "gw123".to_string(),
462 vec![],
463 );
464 assert!(expired_token.is_expired());
465 assert!(!expired_token.is_valid());
466
467 let valid_token = MachineToken::new(
469 "test_token".to_string(),
470 "2099-12-31T23:59:59Z".to_string(),
471 "gw123".to_string(),
472 vec![],
473 );
474 assert!(!valid_token.is_expired());
475 assert!(valid_token.is_valid());
476 }
477
478 #[test]
479 fn test_machine_token_serialization() {
480 let token = MachineToken::new(
481 "test_token".to_string(),
482 "2025-12-31T23:59:59Z".to_string(),
483 "gw123".to_string(),
484 vec!["harmony:send".to_string()],
485 );
486
487 let json = serde_json::to_string(&token).unwrap();
488 assert!(json.contains("\"machine_token\":\"test_token\""));
489 assert!(json.contains("\"gateway_id\":\"gw123\""));
490
491 let deserialized: MachineToken = serde_json::from_str(&json).unwrap();
493 assert_eq!(deserialized.machine_token, token.machine_token);
494 assert_eq!(deserialized.gateway_id, token.gateway_id);
495 }
496
497 #[tokio::test]
498 #[serial]
499 async fn test_save_and_load_token_secure() {
500 let _guard = setup_test_encryption();
501 let instance_id = "test-save-load";
502 let _ = clear_machine_token(instance_id).await;
504
505 let token = MachineToken::new(
506 "test_token_secure".to_string(),
507 "2099-12-31T23:59:59Z".to_string(),
508 "gw_test".to_string(),
509 vec!["harmony:send".to_string()],
510 );
511
512 save_machine_token(instance_id, &token).await.unwrap();
514
515 let loaded = load_machine_token(instance_id).await.unwrap();
517 assert!(loaded.is_some());
518
519 let loaded_token = loaded.unwrap();
520 assert_eq!(loaded_token.machine_token, token.machine_token);
521 assert_eq!(loaded_token.gateway_id, token.gateway_id);
522 assert!(loaded_token.is_valid());
523
524 clear_machine_token(instance_id).await.unwrap();
526 }
527
528 #[tokio::test]
529 #[serial]
530 async fn test_load_nonexistent_token_secure() {
531 let _guard = setup_test_encryption();
532 let instance_id = "test-nonexistent";
533 let _ = clear_machine_token(instance_id).await;
535
536 let result = load_machine_token(instance_id).await.unwrap();
538 assert!(result.is_none());
539 }
540
541 #[tokio::test]
542 #[serial]
543 async fn test_clear_token_secure() {
544 let _guard = setup_test_encryption();
545 let instance_id = "test-clear";
546 let _ = clear_machine_token(instance_id).await;
548
549 let token = MachineToken::new(
550 "test_clear".to_string(),
551 "2099-12-31T23:59:59Z".to_string(),
552 "gw_clear".to_string(),
553 vec![],
554 );
555
556 save_machine_token(instance_id, &token).await.unwrap();
558
559 assert!(load_machine_token(instance_id).await.unwrap().is_some());
561
562 clear_machine_token(instance_id).await.unwrap();
564
565 assert!(load_machine_token(instance_id).await.unwrap().is_none());
567 }
568
569 #[tokio::test]
570 #[serial]
571 async fn test_clear_nonexistent_token_secure() {
572 let _guard = setup_test_encryption();
573 let instance_id = "test-clear-nonexistent";
574 clear_machine_token(instance_id).await.unwrap();
576 }
577
578 #[tokio::test]
579 #[serial]
580 async fn test_token_expiry_detection() {
581 let _guard = setup_test_encryption();
582 let instance_id = "test-expiry";
583 let _ = clear_machine_token(instance_id).await;
584
585 let expired_token = MachineToken::new(
587 "expired_token".to_string(),
588 "2020-01-01T00:00:00Z".to_string(),
589 "gw_expired".to_string(),
590 vec![],
591 );
592
593 save_machine_token(instance_id, &expired_token)
594 .await
595 .unwrap();
596
597 let loaded = load_machine_token(instance_id).await.unwrap();
599 assert!(loaded.is_some());
600 let loaded_token = loaded.unwrap();
601 assert!(loaded_token.is_expired());
602 assert!(!loaded_token.is_valid());
603
604 clear_machine_token(instance_id).await.unwrap();
606 }
607
608 #[tokio::test]
609 #[serial]
610 async fn test_token_with_abilities() {
611 let _guard = setup_test_encryption();
612 let instance_id = "test-abilities";
613 let _ = clear_machine_token(instance_id).await;
614
615 let token = MachineToken::new(
616 "token_with_abilities".to_string(),
617 "2099-12-31T23:59:59Z".to_string(),
618 "gw_abilities".to_string(),
619 vec![
620 "harmony:send".to_string(),
621 "harmony:receive".to_string(),
622 "harmony:config".to_string(),
623 ],
624 );
625
626 save_machine_token(instance_id, &token).await.unwrap();
627
628 let loaded = load_machine_token(instance_id).await.unwrap().unwrap();
629 assert_eq!(loaded.abilities.len(), 3);
630 assert!(loaded.abilities.contains(&"harmony:send".to_string()));
631 assert!(loaded.abilities.contains(&"harmony:receive".to_string()));
632 assert!(loaded.abilities.contains(&"harmony:config".to_string()));
633
634 clear_machine_token(instance_id).await.unwrap();
636 }
637
638 #[tokio::test]
639 #[serial]
640 async fn test_token_overwrites_existing() {
641 let _guard = setup_test_encryption();
642 let instance_id = "test-overwrite";
643 let _ = clear_machine_token(instance_id).await;
644
645 let token1 = MachineToken::new(
647 "first_token".to_string(),
648 "2099-12-31T23:59:59Z".to_string(),
649 "gw_first".to_string(),
650 vec![],
651 );
652 save_machine_token(instance_id, &token1).await.unwrap();
653
654 let token2 = MachineToken::new(
656 "second_token".to_string(),
657 "2099-12-31T23:59:59Z".to_string(),
658 "gw_second".to_string(),
659 vec![],
660 );
661 save_machine_token(instance_id, &token2).await.unwrap();
662
663 let loaded = load_machine_token(instance_id).await.unwrap().unwrap();
665 assert_eq!(loaded.machine_token, "second_token");
666 assert_eq!(loaded.gateway_id, "gw_second");
667
668 clear_machine_token(instance_id).await.unwrap();
670 }
671
672 #[tokio::test]
673 async fn test_token_encrypted_on_disk() {
674 use crate::storage::EncryptedFilesystemStorage;
675 use tempfile::TempDir;
676
677 let instance_id = "test-encryption-verify";
678 let temp_dir = TempDir::new().unwrap();
679
680 let token = MachineToken::new(
682 "super_secret_token_12345".to_string(),
683 "2099-12-31T23:59:59Z".to_string(),
684 "gw_secret".to_string(),
685 vec!["harmony:admin".to_string()],
686 );
687
688 let storage_path = temp_dir.path().join(instance_id);
690 let storage = EncryptedFilesystemStorage::new(&storage_path)
691 .await
692 .unwrap();
693
694 let token_json = serde_json::to_vec(&token).unwrap();
696 storage
697 .write_file_str("auth.json", &token_json)
698 .await
699 .unwrap();
700
701 let token_path = storage_path.join("auth.json");
703
704 assert!(
706 token_path.exists(),
707 "Token file should exist at {:?}",
708 token_path
709 );
710
711 let raw_contents = std::fs::read(&token_path).unwrap();
713 let raw_string = String::from_utf8_lossy(&raw_contents);
714
715 assert!(
717 !raw_string.contains("super_secret_token_12345"),
718 "Token file should NOT contain plaintext token: {}",
719 raw_string
720 );
721 assert!(
722 !raw_string.contains("gw_secret"),
723 "Token file should NOT contain plaintext gateway_id: {}",
724 raw_string
725 );
726 assert!(
727 !raw_string.contains("harmony:admin"),
728 "Token file should NOT contain plaintext abilities: {}",
729 raw_string
730 );
731
732 if raw_contents.len() > 50 {
734 let has_age_header = raw_string.starts_with("age-encryption.org/v1");
736 assert!(
737 has_age_header || raw_contents.starts_with(b"age-encryption.org/v1"),
738 "File should contain age encryption header. Raw contents (first 100 bytes): {:?}",
739 &raw_contents[..std::cmp::min(100, raw_contents.len())]
740 );
741 }
742
743 let decrypted_data = storage.read_file_str("auth.json").await.unwrap();
745 let loaded_token: MachineToken = serde_json::from_slice(&decrypted_data).unwrap();
746 assert_eq!(loaded_token.machine_token, "super_secret_token_12345");
747 assert_eq!(loaded_token.gateway_id, "gw_secret");
748 }
749
750 #[tokio::test]
751 async fn test_token_file_cannot_be_read_as_json() {
752 use crate::storage::EncryptedFilesystemStorage;
753 use tempfile::TempDir;
754
755 let instance_id = "test-raw-json-read";
756 let temp_dir = TempDir::new().unwrap();
757 let storage_path = temp_dir.path().join(instance_id);
758
759 let token = MachineToken::new(
760 "test_token_json".to_string(),
761 "2099-12-31T23:59:59Z".to_string(),
762 "gw_json".to_string(),
763 vec![],
764 );
765
766 let storage = EncryptedFilesystemStorage::new(&storage_path)
768 .await
769 .unwrap();
770 let token_json = serde_json::to_vec(&token).unwrap();
771 storage
772 .write_file_str("auth.json", &token_json)
773 .await
774 .unwrap();
775
776 let token_path = storage_path.join("auth.json");
778
779 let raw_contents = std::fs::read(&token_path).unwrap();
781
782 let json_parse_result: Result<serde_json::Value, _> = serde_json::from_slice(&raw_contents);
784 assert!(
785 json_parse_result.is_err(),
786 "Raw token file should NOT be parseable as JSON (it should be encrypted)"
787 );
788 }
789
790 #[tokio::test]
791 async fn test_token_different_from_plaintext() {
792 use crate::storage::EncryptedFilesystemStorage;
793 use tempfile::TempDir;
794
795 let instance_id = "test-plaintext-compare";
796 let temp_dir = TempDir::new().unwrap();
797 let storage_path = temp_dir.path().join(instance_id);
798
799 let token = MachineToken::new(
800 "comparison_token".to_string(),
801 "2099-12-31T23:59:59Z".to_string(),
802 "gw_compare".to_string(),
803 vec!["test:ability".to_string()],
804 );
805
806 let plaintext_json = serde_json::to_vec(&token).unwrap();
808
809 let storage = EncryptedFilesystemStorage::new(&storage_path)
811 .await
812 .unwrap();
813 storage
814 .write_file_str("auth.json", &plaintext_json)
815 .await
816 .unwrap();
817
818 let token_path = storage_path.join("auth.json");
820 let encrypted_contents = std::fs::read(&token_path).unwrap();
821
822 assert_ne!(
824 encrypted_contents, plaintext_json,
825 "Encrypted file contents should differ from plaintext JSON"
826 );
827
828 assert!(
830 encrypted_contents.len() > plaintext_json.len(),
831 "Encrypted file should be larger due to encryption overhead. Encrypted: {}, Plaintext: {}",
832 encrypted_contents.len(),
833 plaintext_json.len()
834 );
835 }
836
837 #[tokio::test]
838 async fn test_multiple_instances_isolated() {
839 use crate::storage::EncryptedFilesystemStorage;
840 use tempfile::TempDir;
841
842 let temp_dir = TempDir::new().unwrap();
843
844 let token1 = MachineToken::new(
846 "token_instance_1".to_string(),
847 "2099-12-31T23:59:59Z".to_string(),
848 "gw_1".to_string(),
849 vec![],
850 );
851
852 let token2 = MachineToken::new(
853 "token_instance_2".to_string(),
854 "2099-12-31T23:59:59Z".to_string(),
855 "gw_2".to_string(),
856 vec![],
857 );
858
859 let storage1_path = temp_dir.path().join("instance-1");
861 let storage2_path = temp_dir.path().join("instance-2");
862
863 let storage1 = EncryptedFilesystemStorage::new(&storage1_path)
864 .await
865 .unwrap();
866 let storage2 = EncryptedFilesystemStorage::new(&storage2_path)
867 .await
868 .unwrap();
869
870 let token1_json = serde_json::to_vec(&token1).unwrap();
872 let token2_json = serde_json::to_vec(&token2).unwrap();
873 storage1
874 .write_file_str("auth.json", &token1_json)
875 .await
876 .unwrap();
877 storage2
878 .write_file_str("auth.json", &token2_json)
879 .await
880 .unwrap();
881
882 let path1 = storage1_path.join("auth.json");
884 let path2 = storage2_path.join("auth.json");
885
886 assert!(path1.exists(), "Instance 1 token file should exist");
887 assert!(path2.exists(), "Instance 2 token file should exist");
888 assert_ne!(path1, path2, "Token files should be in different locations");
889
890 let key1_path = storage1_path.join("encryption.key");
892 let key2_path = storage2_path.join("encryption.key");
893
894 if key1_path.exists() && key2_path.exists() {
895 let key1_contents = std::fs::read(&key1_path).unwrap();
896 let key2_contents = std::fs::read(&key2_path).unwrap();
897 assert_ne!(
898 key1_contents, key2_contents,
899 "Encryption keys should be different for each instance"
900 );
901 }
902
903 let decrypted1 = storage1.read_file_str("auth.json").await.unwrap();
905 let decrypted2 = storage2.read_file_str("auth.json").await.unwrap();
906
907 let loaded1: MachineToken = serde_json::from_slice(&decrypted1).unwrap();
908 let loaded2: MachineToken = serde_json::from_slice(&decrypted2).unwrap();
909
910 assert_eq!(loaded1.machine_token, "token_instance_1");
911 assert_eq!(loaded1.gateway_id, "gw_1");
912 assert_eq!(loaded2.machine_token, "token_instance_2");
913 assert_eq!(loaded2.gateway_id, "gw_2");
914 }
915
916 #[tokio::test]
917 #[cfg(unix)]
918 async fn test_encryption_key_file_permissions() {
919 use crate::storage::EncryptedFilesystemStorage;
920 use std::os::unix::fs::PermissionsExt;
921 use tempfile::TempDir;
922
923 let instance_id = "test-key-permissions";
924 let temp_dir = TempDir::new().unwrap();
925 let storage_path = temp_dir.path().join(instance_id);
926
927 let _storage = EncryptedFilesystemStorage::new(&storage_path)
929 .await
930 .unwrap();
931
932 let key_path = storage_path.join("encryption.key");
934
935 if !key_path.exists() {
937 return;
939 }
940
941 let metadata = std::fs::metadata(&key_path).unwrap();
942 let permissions = metadata.permissions();
943 let mode = permissions.mode();
944
945 let permission_bits = mode & 0o777;
947 assert_eq!(
948 permission_bits, 0o600,
949 "Encryption key file should have 0600 permissions (owner read/write only), got {:o}",
950 permission_bits
951 );
952 }
953
954 #[tokio::test]
955 async fn test_tampered_token_file_fails_to_load() {
956 use crate::storage::EncryptedFilesystemStorage;
957 use tempfile::TempDir;
958
959 let instance_id = "test-tamper";
960 let temp_dir = TempDir::new().unwrap();
961 let storage_path = temp_dir.path().join(instance_id);
962
963 let token = MachineToken::new(
964 "original_token".to_string(),
965 "2099-12-31T23:59:59Z".to_string(),
966 "gw_tamper".to_string(),
967 vec![],
968 );
969
970 let storage = EncryptedFilesystemStorage::new(&storage_path)
972 .await
973 .unwrap();
974 let token_json = serde_json::to_vec(&token).unwrap();
975 storage
976 .write_file_str("auth.json", &token_json)
977 .await
978 .unwrap();
979
980 let token_path = storage_path.join("auth.json");
982 let mut contents = std::fs::read(&token_path).unwrap();
983
984 if contents.len() > 50 {
986 contents[25] = contents[25].wrapping_add(1);
987 contents[30] = contents[30].wrapping_sub(1);
988 std::fs::write(&token_path, contents).unwrap();
989 }
990
991 let result = storage.read_file_str("auth.json").await;
993 assert!(
994 result.is_err(),
995 "Loading tampered encrypted file should fail"
996 );
997 }
998
999 #[tokio::test]
1004 #[serial]
1005 async fn test_generic_save_and_load_user_token() {
1006 use crate::runbeam_api::types::UserToken;
1007 let _guard = setup_test_encryption();
1008 let instance_id = "test-user-token";
1009 clear_token(instance_id, "user_auth").await.ok();
1010
1011 let user_token = UserToken::new(
1012 "user_jwt_token".to_string(),
1013 Some(1234567890),
1014 Some(crate::runbeam_api::types::UserInfo {
1015 id: "user123".to_string(),
1016 name: "Test User".to_string(),
1017 email: "test@example.com".to_string(),
1018 }),
1019 );
1020
1021 save_token(instance_id, "user_auth", &user_token)
1023 .await
1024 .unwrap();
1025
1026 let loaded: Option<UserToken> = load_token(instance_id, "user_auth").await.unwrap();
1028 assert!(loaded.is_some());
1029
1030 let loaded_token = loaded.unwrap();
1031 assert_eq!(loaded_token.token, user_token.token);
1032 assert_eq!(loaded_token.expires_at, user_token.expires_at);
1033 assert!(loaded_token.user.is_some());
1034
1035 clear_token(instance_id, "user_auth").await.unwrap();
1037 }
1038
1039 #[tokio::test]
1040 #[serial]
1041 async fn test_different_token_types_isolated() {
1042 use crate::runbeam_api::types::UserToken;
1043 let _guard = setup_test_encryption();
1044 let instance_id = "test-isolation";
1045
1046 let user_token = UserToken::new("user_token".to_string(), None, None);
1048
1049 let machine_token = MachineToken::new(
1050 "machine_token".to_string(),
1051 "2099-12-31T23:59:59Z".to_string(),
1052 "gw_test".to_string(),
1053 vec![],
1054 );
1055
1056 save_token(instance_id, "user_auth", &user_token)
1058 .await
1059 .unwrap();
1060 save_token(instance_id, "auth", &machine_token)
1061 .await
1062 .unwrap();
1063
1064 let loaded_user: Option<UserToken> = load_token(instance_id, "user_auth").await.unwrap();
1066 let loaded_machine: Option<MachineToken> = load_token(instance_id, "auth").await.unwrap();
1067
1068 assert!(loaded_user.is_some());
1069 assert!(loaded_machine.is_some());
1070 assert_eq!(loaded_user.unwrap().token, "user_token");
1071 assert_eq!(loaded_machine.unwrap().machine_token, "machine_token");
1072
1073 clear_token(instance_id, "user_auth").await.unwrap();
1075 clear_token(instance_id, "auth").await.unwrap();
1076 }
1077
1078 #[tokio::test]
1079 #[serial]
1080 async fn test_user_token_with_full_metadata() {
1081 use crate::runbeam_api::types::UserToken;
1082 let _guard = setup_test_encryption();
1083 let instance_id = "test-user-full";
1084 clear_token(instance_id, "user_auth").await.ok();
1085
1086 let user_token = UserToken::new(
1087 "detailed_user_token".to_string(),
1088 Some(2000000000),
1089 Some(crate::runbeam_api::types::UserInfo {
1090 id: "user456".to_string(),
1091 name: "John Doe".to_string(),
1092 email: "john@example.com".to_string(),
1093 }),
1094 );
1095
1096 save_token(instance_id, "user_auth", &user_token)
1098 .await
1099 .unwrap();
1100 let loaded: Option<UserToken> = load_token(instance_id, "user_auth").await.unwrap();
1101
1102 assert!(loaded.is_some());
1103 let loaded_token = loaded.unwrap();
1104 assert_eq!(loaded_token.token, "detailed_user_token");
1105 assert_eq!(loaded_token.expires_at, Some(2000000000));
1106
1107 let user_info = loaded_token.user.unwrap();
1108 assert_eq!(user_info.id, "user456");
1109 assert_eq!(user_info.name, "John Doe");
1110 assert_eq!(user_info.email, "john@example.com");
1111
1112 clear_token(instance_id, "user_auth").await.unwrap();
1114 }
1115}