1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use uuid::Uuid;
13
14use crate::error::{BitcoinError, Result};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum AuditSeverity {
19 Info,
21 Warning,
23 Critical,
25 Security,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub enum AuditEventType {
32 TransactionCreated,
34 TransactionSigned,
36 TransactionBroadcasted,
38 TransactionConfirmed,
40 WithdrawalRequested,
42 WithdrawalApproved,
44 WithdrawalRejected,
46 WithdrawalCompleted,
48 AddressGenerated,
50 KeyAccessed,
52 ConfigurationChanged,
54 AdminAction,
56 SecurityAlert,
58 LimitExceeded,
60 Authentication,
62 Authorization,
64 PsbtCreated,
66 PsbtSigned,
68 MultisigOperation,
70 HardwareWalletOperation,
72 Custom(String),
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AuditEvent {
79 pub id: Uuid,
81 pub timestamp: DateTime<Utc>,
83 pub event_type: AuditEventType,
85 pub severity: AuditSeverity,
87 pub actor: String,
89 pub resource: Option<String>,
91 pub action: String,
93 pub metadata: HashMap<String, String>,
95 pub ip_address: Option<String>,
97 pub session_id: Option<String>,
99 pub success: bool,
101 pub error: Option<String>,
103}
104
105impl AuditEvent {
106 pub fn new(
108 event_type: AuditEventType,
109 severity: AuditSeverity,
110 actor: String,
111 action: String,
112 ) -> Self {
113 Self {
114 id: Uuid::new_v4(),
115 timestamp: Utc::now(),
116 event_type,
117 severity,
118 actor,
119 resource: None,
120 action,
121 metadata: HashMap::new(),
122 ip_address: None,
123 session_id: None,
124 success: true,
125 error: None,
126 }
127 }
128
129 pub fn with_resource(mut self, resource: String) -> Self {
131 self.resource = Some(resource);
132 self
133 }
134
135 pub fn with_metadata(mut self, key: String, value: String) -> Self {
137 self.metadata.insert(key, value);
138 self
139 }
140
141 pub fn with_ip_address(mut self, ip: String) -> Self {
143 self.ip_address = Some(ip);
144 self
145 }
146
147 pub fn with_session_id(mut self, session: String) -> Self {
149 self.session_id = Some(session);
150 self
151 }
152
153 pub fn with_error(mut self, error: String) -> Self {
155 self.success = false;
156 self.error = Some(error);
157 self
158 }
159}
160
161#[allow(dead_code)]
163#[async_trait::async_trait]
164pub trait AuditStorage: Send + Sync {
165 async fn store(&self, event: &AuditEvent) -> Result<()>;
167
168 async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>>;
170
171 async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize>;
173}
174
175#[derive(Debug, Clone, Default)]
177pub struct AuditQueryCriteria {
178 pub event_type: Option<AuditEventType>,
180 pub severity: Option<AuditSeverity>,
182 pub actor: Option<String>,
184 pub resource: Option<String>,
186 pub time_from: Option<DateTime<Utc>>,
188 pub time_to: Option<DateTime<Utc>>,
190 pub success: Option<bool>,
192 pub limit: Option<usize>,
194 pub offset: Option<usize>,
196}
197
198#[derive(Clone)]
200pub struct InMemoryAuditStorage {
201 events: Arc<RwLock<Vec<AuditEvent>>>,
202}
203
204impl InMemoryAuditStorage {
205 pub fn new() -> Self {
207 Self {
208 events: Arc::new(RwLock::new(Vec::new())),
209 }
210 }
211
212 pub async fn all_events(&self) -> Vec<AuditEvent> {
214 self.events.read().await.clone()
215 }
216
217 pub async fn clear(&self) {
219 self.events.write().await.clear();
220 }
221}
222
223impl Default for InMemoryAuditStorage {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229#[async_trait::async_trait]
230impl AuditStorage for InMemoryAuditStorage {
231 async fn store(&self, event: &AuditEvent) -> Result<()> {
232 self.events.write().await.push(event.clone());
233 Ok(())
234 }
235
236 async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>> {
237 let events = self.events.read().await;
238 let mut filtered: Vec<AuditEvent> = events
239 .iter()
240 .filter(|e| {
241 if let Some(ref event_type) = criteria.event_type {
242 if &e.event_type != event_type {
243 return false;
244 }
245 }
246 if let Some(ref severity) = criteria.severity {
247 if &e.severity != severity {
248 return false;
249 }
250 }
251 if let Some(ref actor) = criteria.actor {
252 if &e.actor != actor {
253 return false;
254 }
255 }
256 if let Some(ref resource) = criteria.resource {
257 if e.resource.as_ref() != Some(resource) {
258 return false;
259 }
260 }
261 if let Some(time_from) = criteria.time_from {
262 if e.timestamp < time_from {
263 return false;
264 }
265 }
266 if let Some(time_to) = criteria.time_to {
267 if e.timestamp > time_to {
268 return false;
269 }
270 }
271 if let Some(success) = criteria.success {
272 if e.success != success {
273 return false;
274 }
275 }
276 true
277 })
278 .cloned()
279 .collect();
280
281 if let Some(offset) = criteria.offset {
283 filtered = filtered.into_iter().skip(offset).collect();
284 }
285
286 if let Some(limit) = criteria.limit {
288 filtered.truncate(limit);
289 }
290
291 Ok(filtered)
292 }
293
294 async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize> {
295 let events = self.query(criteria).await?;
296 Ok(events.len())
297 }
298}
299
300pub struct FileAuditStorage {
302 file_path: PathBuf,
303}
304
305impl FileAuditStorage {
306 pub fn new(file_path: PathBuf) -> Result<Self> {
308 if let Some(parent) = file_path.parent() {
310 std::fs::create_dir_all(parent).map_err(|e| {
311 BitcoinError::Validation(format!("Failed to create audit log directory: {}", e))
312 })?;
313 }
314
315 Ok(Self { file_path })
316 }
317}
318
319#[async_trait::async_trait]
320impl AuditStorage for FileAuditStorage {
321 async fn store(&self, event: &AuditEvent) -> Result<()> {
322 let json = serde_json::to_string(event).map_err(|e| {
323 BitcoinError::Validation(format!("Failed to serialize audit event: {}", e))
324 })?;
325
326 use std::io::Write;
327 let mut file = std::fs::OpenOptions::new()
328 .create(true)
329 .append(true)
330 .open(&self.file_path)
331 .map_err(|e| {
332 BitcoinError::Validation(format!("Failed to open audit log file: {}", e))
333 })?;
334
335 writeln!(file, "{}", json).map_err(|e| {
336 BitcoinError::Validation(format!("Failed to write to audit log: {}", e))
337 })?;
338
339 Ok(())
340 }
341
342 async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>> {
343 use std::io::{BufRead, BufReader};
344
345 let file = std::fs::File::open(&self.file_path).map_err(|e| {
346 BitcoinError::Validation(format!("Failed to open audit log file: {}", e))
347 })?;
348
349 let reader = BufReader::new(file);
350 let mut filtered = Vec::new();
351
352 for line in reader.lines() {
353 let line = line.map_err(|e| {
354 BitcoinError::Validation(format!("Failed to read audit log line: {}", e))
355 })?;
356
357 let event: AuditEvent = serde_json::from_str(&line).map_err(|e| {
358 BitcoinError::Validation(format!("Failed to parse audit event: {}", e))
359 })?;
360
361 if let Some(ref event_type) = criteria.event_type {
363 if &event.event_type != event_type {
364 continue;
365 }
366 }
367 if let Some(ref severity) = criteria.severity {
368 if &event.severity != severity {
369 continue;
370 }
371 }
372 if let Some(ref actor) = criteria.actor {
373 if &event.actor != actor {
374 continue;
375 }
376 }
377 if let Some(ref resource) = criteria.resource {
378 if event.resource.as_ref() != Some(resource) {
379 continue;
380 }
381 }
382 if let Some(time_from) = criteria.time_from {
383 if event.timestamp < time_from {
384 continue;
385 }
386 }
387 if let Some(time_to) = criteria.time_to {
388 if event.timestamp > time_to {
389 continue;
390 }
391 }
392 if let Some(success) = criteria.success {
393 if event.success != success {
394 continue;
395 }
396 }
397
398 filtered.push(event);
399 }
400
401 if let Some(offset) = criteria.offset {
403 filtered = filtered.into_iter().skip(offset).collect();
404 }
405
406 if let Some(limit) = criteria.limit {
408 filtered.truncate(limit);
409 }
410
411 Ok(filtered)
412 }
413
414 async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize> {
415 let events = self.query(criteria).await?;
416 Ok(events.len())
417 }
418}
419
420pub struct AuditLogger<S: AuditStorage> {
422 storage: Arc<S>,
423 enabled: bool,
424}
425
426impl<S: AuditStorage> AuditLogger<S> {
427 pub fn new(storage: S) -> Self {
429 Self {
430 storage: Arc::new(storage),
431 enabled: true,
432 }
433 }
434
435 pub async fn log(&self, event: AuditEvent) -> Result<()> {
437 if !self.enabled {
438 return Ok(());
439 }
440
441 tracing::info!(
442 event_id = %event.id,
443 event_type = ?event.event_type,
444 severity = ?event.severity,
445 actor = %event.actor,
446 "Audit event logged"
447 );
448
449 self.storage.store(&event).await
450 }
451
452 pub async fn query(&self, criteria: &AuditQueryCriteria) -> Result<Vec<AuditEvent>> {
454 self.storage.query(criteria).await
455 }
456
457 pub async fn count(&self, criteria: &AuditQueryCriteria) -> Result<usize> {
459 self.storage.count(criteria).await
460 }
461
462 pub fn set_enabled(&mut self, enabled: bool) {
464 self.enabled = enabled;
465 }
466}
467
468pub struct AuditEventBuilder {
470 event: AuditEvent,
471}
472
473impl AuditEventBuilder {
474 pub fn new(event_type: AuditEventType, actor: String, action: String) -> Self {
476 Self {
477 event: AuditEvent::new(event_type, AuditSeverity::Info, actor, action),
478 }
479 }
480
481 pub fn severity(mut self, severity: AuditSeverity) -> Self {
483 self.event.severity = severity;
484 self
485 }
486
487 pub fn resource(mut self, resource: String) -> Self {
489 self.event.resource = Some(resource);
490 self
491 }
492
493 pub fn metadata(mut self, key: String, value: String) -> Self {
495 self.event.metadata.insert(key, value);
496 self
497 }
498
499 pub fn ip_address(mut self, ip: String) -> Self {
501 self.event.ip_address = Some(ip);
502 self
503 }
504
505 pub fn session_id(mut self, session: String) -> Self {
507 self.event.session_id = Some(session);
508 self
509 }
510
511 pub fn failed(mut self, error: String) -> Self {
513 self.event.success = false;
514 self.event.error = Some(error);
515 self
516 }
517
518 pub fn build(self) -> AuditEvent {
520 self.event
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
529 fn test_audit_event_creation() {
530 let event = AuditEvent::new(
531 AuditEventType::TransactionCreated,
532 AuditSeverity::Info,
533 "system".to_string(),
534 "Created new transaction".to_string(),
535 );
536
537 assert_eq!(event.event_type, AuditEventType::TransactionCreated);
538 assert_eq!(event.severity, AuditSeverity::Info);
539 assert_eq!(event.actor, "system");
540 assert!(event.success);
541 assert!(event.error.is_none());
542 }
543
544 #[test]
545 fn test_audit_event_builder() {
546 let event = AuditEventBuilder::new(
547 AuditEventType::WithdrawalRequested,
548 "user123".to_string(),
549 "Requested withdrawal of 1 BTC".to_string(),
550 )
551 .severity(AuditSeverity::Critical)
552 .resource("tx_abc123".to_string())
553 .metadata("amount".to_string(), "100000000".to_string())
554 .build();
555
556 assert_eq!(event.severity, AuditSeverity::Critical);
557 assert_eq!(event.resource, Some("tx_abc123".to_string()));
558 assert_eq!(event.metadata.get("amount"), Some(&"100000000".to_string()));
559 }
560
561 #[tokio::test]
562 async fn test_in_memory_storage() {
563 let storage = InMemoryAuditStorage::new();
564 let event = AuditEvent::new(
565 AuditEventType::TransactionCreated,
566 AuditSeverity::Info,
567 "test".to_string(),
568 "test action".to_string(),
569 );
570
571 storage.store(&event).await.unwrap();
572
573 let events = storage.all_events().await;
574 assert_eq!(events.len(), 1);
575 assert_eq!(events[0].actor, "test");
576 }
577
578 #[tokio::test]
579 async fn test_audit_query() {
580 let storage = InMemoryAuditStorage::new();
581
582 for i in 0..5 {
584 let event = AuditEvent::new(
585 AuditEventType::TransactionCreated,
586 AuditSeverity::Info,
587 format!("user{}", i),
588 "test".to_string(),
589 );
590 storage.store(&event).await.unwrap();
591 }
592
593 let criteria = AuditQueryCriteria {
595 actor: Some("user2".to_string()),
596 ..Default::default()
597 };
598
599 let results = storage.query(&criteria).await.unwrap();
600 assert_eq!(results.len(), 1);
601 assert_eq!(results[0].actor, "user2");
602 }
603
604 #[tokio::test]
605 async fn test_audit_logger() {
606 let storage = InMemoryAuditStorage::new();
607 let logger = AuditLogger::new(storage.clone());
608
609 let event = AuditEvent::new(
610 AuditEventType::SecurityAlert,
611 AuditSeverity::Security,
612 "system".to_string(),
613 "Suspicious activity detected".to_string(),
614 );
615
616 logger.log(event).await.unwrap();
617
618 let events = storage.all_events().await;
619 assert_eq!(events.len(), 1);
620 assert_eq!(events[0].severity, AuditSeverity::Security);
621 }
622}