tap_node/validation/
mod.rs

1//! Message validation framework for TAP Node
2//!
3//! This module provides a comprehensive validation system for incoming TAP messages.
4//! It includes validators for:
5//! - Message uniqueness (preventing duplicate messages)
6//! - Timestamp validation (messages not too far in future/past)
7//! - Agent authorization (only authorized agents can respond to transactions)
8//! - Message expiry validation
9
10use crate::storage::Storage;
11use async_trait::async_trait;
12use std::sync::Arc;
13use tap_msg::didcomm::PlainMessage;
14
15pub mod agent_validator;
16pub mod timestamp_validator;
17pub mod uniqueness_validator;
18
19/// Result of message validation
20#[derive(Debug, Clone)]
21pub enum ValidationResult {
22    /// Message passed validation
23    Accept,
24    /// Message failed validation with reason
25    Reject(String),
26}
27
28/// Trait for message validators
29#[async_trait]
30pub trait MessageValidator: Send + Sync {
31    /// Validate a message
32    ///
33    /// Returns Accept if the message passes validation,
34    /// or Reject with a reason if it fails.
35    async fn validate(&self, message: &PlainMessage) -> ValidationResult;
36}
37
38/// Composite validator that runs multiple validators
39pub struct CompositeValidator {
40    validators: Vec<Box<dyn MessageValidator>>,
41}
42
43impl CompositeValidator {
44    /// Create a new composite validator
45    pub fn new(validators: Vec<Box<dyn MessageValidator>>) -> Self {
46        Self { validators }
47    }
48}
49
50#[async_trait]
51impl MessageValidator for CompositeValidator {
52    async fn validate(&self, message: &PlainMessage) -> ValidationResult {
53        for validator in &self.validators {
54            match validator.validate(message).await {
55                ValidationResult::Accept => continue,
56                ValidationResult::Reject(reason) => return ValidationResult::Reject(reason),
57            }
58        }
59        ValidationResult::Accept
60    }
61}
62
63/// Standard validator configuration
64pub struct StandardValidatorConfig {
65    /// Maximum allowed timestamp drift in seconds
66    pub max_timestamp_drift_secs: i64,
67    /// Storage for uniqueness and agent checks
68    pub storage: Arc<Storage>,
69}
70
71// Note: StandardValidatorConfig doesn't have a Default implementation
72// because storage must be provided by the caller
73
74/// Create a standard validator with all recommended validators
75pub async fn create_standard_validator(config: StandardValidatorConfig) -> CompositeValidator {
76    let validators: Vec<Box<dyn MessageValidator>> = vec![
77        Box::new(timestamp_validator::TimestampValidator::new(
78            config.max_timestamp_drift_secs,
79        )),
80        Box::new(uniqueness_validator::UniquenessValidator::new(
81            config.storage.clone(),
82        )),
83        Box::new(agent_validator::AgentAuthorizationValidator::new(
84            config.storage.clone(),
85        )),
86    ];
87
88    CompositeValidator::new(validators)
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    struct AlwaysAcceptValidator;
96
97    #[async_trait]
98    impl MessageValidator for AlwaysAcceptValidator {
99        async fn validate(&self, _message: &PlainMessage) -> ValidationResult {
100            ValidationResult::Accept
101        }
102    }
103
104    struct AlwaysRejectValidator {
105        reason: String,
106    }
107
108    #[async_trait]
109    impl MessageValidator for AlwaysRejectValidator {
110        async fn validate(&self, _message: &PlainMessage) -> ValidationResult {
111            ValidationResult::Reject(self.reason.clone())
112        }
113    }
114
115    #[tokio::test]
116    async fn test_composite_validator_all_accept() {
117        let validators: Vec<Box<dyn MessageValidator>> = vec![
118            Box::new(AlwaysAcceptValidator),
119            Box::new(AlwaysAcceptValidator),
120        ];
121
122        let composite = CompositeValidator::new(validators);
123        let message = PlainMessage::new(
124            "test_msg_1".to_string(),
125            "test_type".to_string(),
126            serde_json::json!({}),
127            "did:example:sender".to_string(),
128        )
129        .with_recipient("did:example:receiver");
130
131        match composite.validate(&message).await {
132            ValidationResult::Accept => {} // Expected
133            ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
134        }
135    }
136
137    #[tokio::test]
138    async fn test_composite_validator_one_reject() {
139        let validators: Vec<Box<dyn MessageValidator>> = vec![
140            Box::new(AlwaysAcceptValidator),
141            Box::new(AlwaysRejectValidator {
142                reason: "Test rejection".to_string(),
143            }),
144            Box::new(AlwaysAcceptValidator),
145        ];
146
147        let composite = CompositeValidator::new(validators);
148        let message = PlainMessage::new(
149            "test_msg_1".to_string(),
150            "test_type".to_string(),
151            serde_json::json!({}),
152            "did:example:sender".to_string(),
153        )
154        .with_recipient("did:example:receiver");
155
156        match composite.validate(&message).await {
157            ValidationResult::Accept => panic!("Expected reject, got accept"),
158            ValidationResult::Reject(reason) => assert_eq!(reason, "Test rejection"),
159        }
160    }
161}