fraiseql_server/encryption/
database_adapter.rs1use std::{collections::HashMap, sync::Arc};
17
18use tokio::sync::RwLock;
19
20use super::FieldEncryption;
21use crate::secrets_manager::{SecretsError, SecretsManager};
22
23#[allow(async_fn_in_trait)]
28pub trait EncryptedFieldAdapter: Send + Sync {
29 fn get_encrypted_fields(&self) -> Vec<String>;
31
32 fn is_encrypted(&self, field_name: &str) -> bool {
34 self.get_encrypted_fields().contains(&field_name.to_string())
35 }
36
37 async fn encrypt_value(
39 &self,
40 field_name: &str,
41 plaintext: &str,
42 ) -> Result<Vec<u8>, SecretsError>;
43
44 async fn decrypt_value(
46 &self,
47 field_name: &str,
48 ciphertext: &[u8],
49 ) -> Result<String, SecretsError>;
50
51 async fn encrypt_with_context(
53 &self,
54 field_name: &str,
55 plaintext: &str,
56 context: &str,
57 ) -> Result<Vec<u8>, SecretsError>;
58
59 async fn decrypt_with_context(
61 &self,
62 field_name: &str,
63 ciphertext: &[u8],
64 context: &str,
65 ) -> Result<String, SecretsError>;
66}
67
68#[derive(Debug, Clone)]
72pub struct EncryptionContext {
73 pub user_id: String,
75 pub field_name: String,
77 pub operation: String,
79 pub timestamp: String,
81}
82
83impl EncryptionContext {
84 pub fn new(
86 user_id: impl Into<String>,
87 field_name: impl Into<String>,
88 operation: impl Into<String>,
89 timestamp: impl Into<String>,
90 ) -> Self {
91 Self {
92 user_id: user_id.into(),
93 field_name: field_name.into(),
94 operation: operation.into(),
95 timestamp: timestamp.into(),
96 }
97 }
98
99 pub fn to_aad_string(&self) -> String {
101 format!(
102 "user:{}:field:{}:op:{}:ts:{}",
103 self.user_id, self.field_name, self.operation, self.timestamp
104 )
105 }
106}
107
108#[derive(Clone)]
110struct CachedEncryption {
111 cipher: FieldEncryption,
112 #[allow(dead_code)]
114 key_name: String,
115}
116
117pub struct DatabaseFieldAdapter {
122 secrets_manager: Arc<SecretsManager>,
124 field_keys: HashMap<String, String>,
126 ciphers: Arc<RwLock<HashMap<String, CachedEncryption>>>,
128}
129
130impl DatabaseFieldAdapter {
131 pub fn new(secrets_manager: Arc<SecretsManager>, field_keys: HashMap<String, String>) -> Self {
148 Self {
149 secrets_manager,
150 field_keys,
151 ciphers: Arc::new(RwLock::new(HashMap::new())),
152 }
153 }
154
155 async fn get_cipher(&self, field_name: &str) -> Result<FieldEncryption, SecretsError> {
157 let cache = self.ciphers.read().await;
159 if let Some(cached) = cache.get(field_name) {
160 return Ok(cached.cipher.clone());
161 }
162 drop(cache);
163
164 let key_name = self.field_keys.get(field_name).ok_or_else(|| {
166 SecretsError::NotFound(format!(
167 "Encryption key for field '{}' not configured",
168 field_name
169 ))
170 })?;
171
172 let key_str = self.secrets_manager.get_secret(key_name).await?;
173 let key_bytes = key_str.as_bytes().to_vec();
174
175 if key_bytes.len() != 32 {
176 return Err(SecretsError::ValidationError(format!(
177 "Encryption key for field '{}' must be 32 bytes, got {}",
178 field_name,
179 key_bytes.len()
180 )));
181 }
182
183 let cipher = FieldEncryption::new(&key_bytes);
184
185 let mut cache = self.ciphers.write().await;
187 cache.insert(
188 field_name.to_string(),
189 CachedEncryption {
190 cipher: cipher.clone(),
191 key_name: key_name.clone(),
192 },
193 );
194
195 Ok(cipher)
196 }
197
198 pub fn register_field(&mut self, field_name: impl Into<String>, key_name: impl Into<String>) {
205 self.field_keys.insert(field_name.into(), key_name.into());
206 }
207
208 pub async fn invalidate_cache(&self) {
213 let mut cache = self.ciphers.write().await;
214 cache.clear();
215 }
216
217 pub async fn invalidate_field_cache(&self, field_name: &str) {
223 let mut cache = self.ciphers.write().await;
224 cache.remove(field_name);
225 }
226
227 pub async fn cache_size(&self) -> usize {
231 self.ciphers.read().await.len()
232 }
233}
234
235impl EncryptedFieldAdapter for DatabaseFieldAdapter {
236 fn get_encrypted_fields(&self) -> Vec<String> {
237 self.field_keys.keys().cloned().collect()
238 }
239
240 async fn encrypt_value(
241 &self,
242 field_name: &str,
243 plaintext: &str,
244 ) -> Result<Vec<u8>, SecretsError> {
245 let cipher = self.get_cipher(field_name).await.map_err(|e| {
246 SecretsError::EncryptionError(format!(
247 "Failed to get encryption cipher for field '{}': {}",
248 field_name, e
249 ))
250 })?;
251
252 cipher.encrypt(plaintext).map_err(|e| {
253 SecretsError::EncryptionError(format!(
254 "Failed to encrypt value for field '{}': {}",
255 field_name, e
256 ))
257 })
258 }
259
260 async fn decrypt_value(
261 &self,
262 field_name: &str,
263 ciphertext: &[u8],
264 ) -> Result<String, SecretsError> {
265 let cipher = self.get_cipher(field_name).await.map_err(|e| {
266 SecretsError::EncryptionError(format!(
267 "Failed to get decryption cipher for field '{}': {}",
268 field_name, e
269 ))
270 })?;
271
272 cipher.decrypt(ciphertext).map_err(|e| {
273 SecretsError::EncryptionError(format!(
274 "Failed to decrypt value for field '{}': {}",
275 field_name, e
276 ))
277 })
278 }
279
280 async fn encrypt_with_context(
281 &self,
282 field_name: &str,
283 plaintext: &str,
284 context: &str,
285 ) -> Result<Vec<u8>, SecretsError> {
286 let cipher = self.get_cipher(field_name).await.map_err(|e| {
287 SecretsError::EncryptionError(format!(
288 "Failed to get encryption cipher for field '{}': {}",
289 field_name, e
290 ))
291 })?;
292
293 cipher.encrypt_with_context(plaintext, context).map_err(|e| {
294 SecretsError::EncryptionError(format!(
295 "Failed to encrypt value with context for field '{}': {}",
296 field_name, e
297 ))
298 })
299 }
300
301 async fn decrypt_with_context(
302 &self,
303 field_name: &str,
304 ciphertext: &[u8],
305 context: &str,
306 ) -> Result<String, SecretsError> {
307 let cipher = self.get_cipher(field_name).await.map_err(|e| {
308 SecretsError::EncryptionError(format!(
309 "Failed to get decryption cipher for field '{}': {}",
310 field_name, e
311 ))
312 })?;
313
314 cipher.decrypt_with_context(ciphertext, context).map_err(|e| {
315 SecretsError::EncryptionError(format!(
316 "Failed to decrypt value with context for field '{}': {}",
317 field_name, e
318 ))
319 })
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_encryption_context_creation() {
329 let ctx = EncryptionContext::new("user123", "email", "insert", "2024-01-01T00:00:00Z");
330 assert_eq!(ctx.user_id, "user123");
331 assert_eq!(ctx.field_name, "email");
332 assert_eq!(ctx.operation, "insert");
333 }
334
335 #[test]
336 fn test_encryption_context_aad_string() {
337 let ctx = EncryptionContext::new("user456", "phone", "update", "2024-01-02T12:00:00Z");
338 let aad = ctx.to_aad_string();
339 assert!(aad.contains("user:user456"));
340 assert!(aad.contains("field:phone"));
341 assert!(aad.contains("op:update"));
342 assert!(aad.contains("ts:2024-01-02T12:00:00Z"));
343 }
344
345 #[tokio::test]
346 #[ignore] async fn test_adapter_get_cipher_caching() {
348 assert!(true);
351 }
352
353 #[tokio::test]
354 #[ignore]
355 async fn test_adapter_multiple_keys() {
356 assert!(true);
360 }
361
362 #[tokio::test]
363 #[ignore]
364 async fn test_adapter_cache_invalidation() {
365 assert!(true);
369 }
370
371 #[tokio::test]
372 #[ignore]
373 async fn test_adapter_missing_key_error() {
374 assert!(true);
378 }
379
380 #[tokio::test]
381 #[ignore]
382 async fn test_adapter_is_encrypted_check() {
383 assert!(true);
387 }
388}