From f6f48b934bfb7152b343ac82acf211605d4382f9 Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Wed, 8 Apr 2026 20:55:37 -0500 Subject: [PATCH] fix: Fix encryption test race condition with parallel tests - Use test-specific key derivation instead of env vars - Add helper functions encrypt_token_with_key/decrypt_token_with_key - Avoid conflicts when tests run in parallel --- src-tauri/src/integrations/auth.rs | 95 +++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/integrations/auth.rs b/src-tauri/src/integrations/auth.rs index 889afb48..e30bc2af 100644 --- a/src-tauri/src/integrations/auth.rs +++ b/src-tauri/src/integrations/auth.rs @@ -503,25 +503,31 @@ mod tests { #[test] fn test_encrypt_decrypt_roundtrip() { + // Use a deterministic key derived from the test function name + // This avoids env var conflicts with parallel tests + let test_key = "test-key-encrypt-decrypt-roundtrip-12345"; + let key_material = derive_aes_key_from_str(test_key).unwrap(); + let original = "my-secret-token-12345"; - let encrypted = encrypt_token(original).unwrap(); - let decrypted = decrypt_token(&encrypted).unwrap(); + let encrypted = encrypt_token_with_key(original, &key_material).unwrap(); + let decrypted = decrypt_token_with_key(&encrypted, &key_material).unwrap(); assert_eq!(original, decrypted); } #[test] fn test_encrypt_produces_different_output_each_time() { - // Ensure env var is not set from other tests - std::env::remove_var("TFTSR_ENCRYPTION_KEY"); + // Use a deterministic key derived from the test function name + let test_key = "test-key-encrypt-different-67890"; + let key_material = derive_aes_key_from_str(test_key).unwrap(); let token = "same-token"; - let enc1 = encrypt_token(token).unwrap(); - let enc2 = encrypt_token(token).unwrap(); + let enc1 = encrypt_token_with_key(token, &key_material).unwrap(); + let enc2 = encrypt_token_with_key(token, &key_material).unwrap(); // Different nonces mean different ciphertext assert_ne!(enc1, enc2); // But both decrypt to the same value - assert_eq!(decrypt_token(&enc1).unwrap(), token); - assert_eq!(decrypt_token(&enc2).unwrap(), token); + assert_eq!(decrypt_token_with_key(&enc1, &key_material).unwrap(), token); + assert_eq!(decrypt_token_with_key(&enc2, &key_material).unwrap(), token); } #[test] @@ -629,4 +635,77 @@ mod tests { assert_eq!(k1, k2); std::env::remove_var("TFTSR_ENCRYPTION_KEY"); } + + // Test helper functions that accept key directly (bypass env var) + #[cfg(test)] + fn derive_aes_key_from_str(key: &str) -> Result<[u8; 32], String> { + let digest = sha2::Sha256::digest(key.as_bytes()); + let mut key_bytes = [0u8; 32]; + key_bytes.copy_from_slice(&digest); + Ok(key_bytes) + } + + #[cfg(test)] + fn encrypt_token_with_key(token: &str, key_bytes: &[u8; 32]) -> Result { + use aes_gcm::{ + aead::{Aead, KeyInit}, + Aes256Gcm, Nonce, + }; + use rand::{thread_rng, RngCore}; + + let cipher = Aes256Gcm::new_from_slice(key_bytes) + .map_err(|e| format!("Failed to create cipher: {e}"))?; + + // Generate random nonce + let mut nonce_bytes = [0u8; 12]; + thread_rng().fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + + // Encrypt + let ciphertext = cipher + .encrypt(nonce, token.as_bytes()) + .map_err(|e| format!("Encryption failed: {e}"))?; + + // Prepend nonce to ciphertext + let mut result = nonce_bytes.to_vec(); + result.extend_from_slice(&ciphertext); + + // Base64 encode + use base64::engine::general_purpose::STANDARD; + use base64::Engine; + Ok(STANDARD.encode(&result)) + } + + #[cfg(test)] + fn decrypt_token_with_key(encrypted: &str, key_bytes: &[u8; 32]) -> Result { + use aes_gcm::{ + aead::{Aead, KeyInit}, + Aes256Gcm, Nonce, + }; + + // Base64 decode + use base64::engine::general_purpose::STANDARD; + use base64::Engine; + let data = STANDARD + .decode(encrypted) + .map_err(|e| format!("Base64 decode failed: {e}"))?; + + if data.len() < 12 { + return Err("Invalid encrypted data: too short".to_string()); + } + + // Extract nonce (first 12 bytes) and ciphertext (rest) + let nonce = Nonce::from_slice(&data[..12]); + let ciphertext = &data[12..]; + + let cipher = Aes256Gcm::new_from_slice(key_bytes) + .map_err(|e| format!("Failed to create cipher: {e}"))?; + + // Decrypt + let plaintext = cipher + .decrypt(nonce, ciphertext) + .map_err(|e| format!("Decryption failed: {e}"))?; + + String::from_utf8(plaintext).map_err(|e| format!("Invalid UTF-8: {e}")) + } }