pulseengine_mcp_security_middleware/
lib.rs1pub mod auth;
86pub mod config;
87pub mod error;
88pub mod middleware;
89pub mod profiles;
90pub mod utils;
91
92pub use auth::{ApiKeyValidator, AuthContext, TokenValidator};
94pub use config::SecurityConfig;
95pub use error::{SecurityError, SecurityResult};
96pub use middleware::{SecurityMiddleware, mcp_auth_middleware, mcp_rate_limit_middleware};
97pub use profiles::SecurityProfile;
98pub use profiles::{DevelopmentProfile, ProductionProfile, StagingProfile};
99pub use utils::{SecureRandom, generate_api_key, generate_jwt_secret};
100
101pub const VERSION: &str = env!("CARGO_PKG_VERSION");
103
104pub fn dev_security() -> SecurityConfig {
116 SecurityConfig::development()
117}
118
119pub fn prod_security() -> SecurityConfig {
131 SecurityConfig::production()
132}
133
134pub fn env_security() -> SecurityResult<SecurityConfig> {
147 SecurityConfig::from_env()
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_dev_security_creation() {
156 let config = dev_security();
157 assert_eq!(config.profile, SecurityProfile::Development);
158 }
159
160 #[test]
161 fn test_prod_security_creation() {
162 let config = prod_security();
163 assert_eq!(config.profile, SecurityProfile::Production);
164 }
165
166 #[test]
167 fn test_version_format() {
168 assert!(
170 VERSION.chars().any(|c| c.is_ascii_digit()),
171 "VERSION should contain digits: {VERSION}"
172 );
173 }
174
175 #[test]
176 fn test_env_security_with_invalid_profile() {
177 use std::env;
178
179 unsafe {
181 env::set_var("MCP_SECURITY_PROFILE", "invalid");
182 }
183
184 let result = env_security();
185 assert!(result.is_err(), "Should fail with invalid profile");
186
187 unsafe {
189 env::remove_var("MCP_SECURITY_PROFILE");
190 }
191 }
192
193 #[test]
194 fn test_env_security_with_valid_profiles() {
195 use std::env;
196
197 for profile in &["development", "staging", "production"] {
198 unsafe {
199 env::set_var("MCP_SECURITY_PROFILE", profile);
200 }
201 let result = env_security();
202 assert!(result.is_ok(), "Should succeed with profile {profile}");
203 unsafe {
204 env::remove_var("MCP_SECURITY_PROFILE");
205 }
206 }
207 }
208
209 #[test]
210 fn test_version_constant() {
211 assert!(VERSION.contains('.'), "Version should contain dots");
213 assert!(
214 VERSION.chars().any(char::is_numeric),
215 "Version should contain numbers"
216 );
217 }
218
219 #[test]
220 fn test_module_exports() {
221 let _config = dev_security();
223 let _prod_config = prod_security();
224
225 use crate::profiles::SecurityProfile;
227 let _dev_profile = SecurityProfile::Development;
228 let _staging_profile = SecurityProfile::Staging;
229 let _prod_profile = SecurityProfile::Production;
230 }
231
232 #[test]
233 fn test_error_constructors() {
234 use crate::error::SecurityError;
235
236 let config_err = SecurityError::config("test config error");
237 assert!(config_err.to_string().contains("test config error"));
238
239 let auth_err = SecurityError::auth("test auth error");
240 assert!(auth_err.to_string().contains("test auth error"));
241
242 let authz_err = SecurityError::authz("test authz error");
243 assert!(authz_err.to_string().contains("test authz error"));
244
245 let token_err = SecurityError::invalid_token("test token error");
246 assert!(token_err.to_string().contains("test token error"));
247
248 let jwt_err = SecurityError::jwt("test jwt error");
249 assert!(jwt_err.to_string().contains("test jwt error"));
250
251 let random_err = SecurityError::random("test random error");
252 assert!(random_err.to_string().contains("test random error"));
253
254 let crypto_err = SecurityError::crypto("test crypto error");
255 assert!(crypto_err.to_string().contains("test crypto error"));
256
257 let http_err = SecurityError::http("test http error");
258 assert!(http_err.to_string().contains("test http error"));
259
260 let internal_err = SecurityError::internal("test internal error");
261 assert!(internal_err.to_string().contains("test internal error"));
262 }
263
264 #[test]
265 fn test_security_config_methods() {
266 use crate::config::SecurityConfig;
267 use crate::profiles::SecurityProfile;
268
269 let dev_config = SecurityConfig::development();
271 assert_eq!(dev_config.profile, SecurityProfile::Development);
272
273 let staging_config = SecurityConfig::staging();
274 assert_eq!(staging_config.profile, SecurityProfile::Staging);
275
276 let prod_config = SecurityConfig::production();
277 assert_eq!(prod_config.profile, SecurityProfile::Production);
278
279 let config = SecurityConfig::development()
281 .with_api_key("test_key")
282 .with_jwt_secret("test_secret")
283 .with_jwt_issuer("test_issuer")
284 .with_jwt_audience("test_audience");
285
286 assert_eq!(config.api_key.as_ref().unwrap(), "test_key");
287 assert_eq!(config.jwt_secret.as_ref().unwrap(), "test_secret");
288 assert_eq!(config.jwt_issuer, "test_issuer");
289 assert_eq!(config.jwt_audience, "test_audience");
290 }
291
292 #[test]
293 fn test_additional_utility_functions() {
294 use crate::utils::{SecureRandom, generate_request_id, generate_session_id};
295
296 let session1 = generate_session_id();
298 let session2 = generate_session_id();
299 assert_ne!(session1, session2);
300 assert!(session1.len() > 10);
301
302 let request1 = generate_request_id();
304 let request2 = generate_request_id();
305 assert_ne!(request1, request2);
306 assert!(request1.len() > 10);
307
308 let b64_str1 = SecureRandom::base64_string(32);
310 let b64_str2 = SecureRandom::base64_string(32);
311 assert_ne!(b64_str1, b64_str2);
312 assert!(b64_str1.len() > 40); let b64_url_str1 = SecureRandom::base64_url_string(32);
316 let b64_url_str2 = SecureRandom::base64_url_string(32);
317 assert_ne!(b64_url_str1, b64_url_str2);
318 assert!(b64_url_str1.len() > 40);
319 assert!(!b64_url_str1.contains('+'));
320 assert!(!b64_url_str1.contains('/'));
321 }
322}