pulseengine_mcp_auth/setup/
mod.rs

1//! Setup and initialization utilities
2//!
3//! This module provides utilities for system validation, setup, and initialization
4//! of the authentication framework.
5
6pub mod validator;
7
8use crate::config::StorageConfig;
9use crate::{AuthConfig, AuthenticationManager, Role, ValidationConfig};
10use std::path::{Path, PathBuf};
11use thiserror::Error;
12
13/// Setup errors
14#[derive(Debug, Error)]
15pub enum SetupError {
16    #[error("System validation failed: {0}")]
17    ValidationFailed(String),
18
19    #[error("Configuration error: {0}")]
20    ConfigError(String),
21
22    #[error("Storage initialization failed: {0}")]
23    StorageError(String),
24
25    #[error("Key generation failed: {0}")]
26    KeyGenerationError(String),
27
28    #[error("Environment error: {0}")]
29    EnvironmentError(String),
30}
31
32/// Setup configuration builder
33pub struct SetupBuilder {
34    master_key: Option<String>,
35    storage_config: Option<StorageConfig>,
36    validation_config: Option<ValidationConfig>,
37    create_admin_key: bool,
38    admin_key_name: String,
39    admin_ip_whitelist: Option<Vec<String>>,
40}
41
42impl Default for SetupBuilder {
43    fn default() -> Self {
44        Self {
45            master_key: None,
46            storage_config: None,
47            validation_config: None,
48            create_admin_key: true,
49            admin_key_name: "admin".to_string(),
50            admin_ip_whitelist: None,
51        }
52    }
53}
54
55impl SetupBuilder {
56    /// Create a new setup builder
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Set the master encryption key
62    pub fn with_master_key(mut self, key: String) -> Self {
63        self.master_key = Some(key);
64        self
65    }
66
67    /// Use an existing master key from the environment
68    pub fn with_env_master_key(mut self) -> Result<Self, SetupError> {
69        match std::env::var("PULSEENGINE_MCP_MASTER_KEY") {
70            Ok(key) => {
71                self.master_key = Some(key);
72                Ok(self)
73            }
74            Err(_) => Err(SetupError::EnvironmentError(
75                "PULSEENGINE_MCP_MASTER_KEY not found".to_string(),
76            )),
77        }
78    }
79
80    /// Set the storage configuration
81    pub fn with_storage(mut self, config: StorageConfig) -> Self {
82        self.storage_config = Some(config);
83        self
84    }
85
86    /// Use default file storage
87    pub fn with_default_storage(self) -> Self {
88        let path = dirs::home_dir()
89            .unwrap_or_else(|| PathBuf::from("."))
90            .join(".pulseengine")
91            .join("mcp-auth")
92            .join("keys.enc");
93
94        self.with_storage(StorageConfig::File {
95            path,
96            file_permissions: 0o600,
97            dir_permissions: 0o700,
98            require_secure_filesystem: true,
99            enable_filesystem_monitoring: false,
100        })
101    }
102
103    /// Set validation configuration
104    pub fn with_validation(mut self, config: ValidationConfig) -> Self {
105        self.validation_config = Some(config);
106        self
107    }
108
109    /// Configure admin key creation
110    pub fn with_admin_key(mut self, name: String, ip_whitelist: Option<Vec<String>>) -> Self {
111        self.create_admin_key = true;
112        self.admin_key_name = name;
113        self.admin_ip_whitelist = ip_whitelist;
114        self
115    }
116
117    /// Skip admin key creation
118    pub fn skip_admin_key(mut self) -> Self {
119        self.create_admin_key = false;
120        self
121    }
122
123    /// Build and initialize the authentication system
124    pub async fn build(self) -> Result<SetupResult, SetupError> {
125        // Validate system requirements
126        validator::validate_system()?;
127
128        // Generate or use master key
129        let master_key = match self.master_key {
130            Some(key) => key,
131            None => generate_master_key()?,
132        };
133
134        // Set master key in environment for this process
135        std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", &master_key);
136
137        // Use storage config or default
138        let storage_config = self
139            .storage_config
140            .unwrap_or_else(|| create_default_storage_config());
141
142        // Use validation config or default
143        let validation_config = self.validation_config.unwrap_or_default();
144
145        // Create auth config
146        let auth_config = AuthConfig {
147            enabled: true,
148            storage: storage_config.clone(),
149            cache_size: 1000,
150            session_timeout_secs: validation_config.session_timeout_minutes * 60,
151            max_failed_attempts: validation_config.max_failed_attempts,
152            rate_limit_window_secs: validation_config.failed_attempt_window_minutes * 60,
153        };
154
155        // Initialize authentication manager
156        let auth_manager =
157            AuthenticationManager::new_with_validation(auth_config, validation_config)
158                .await
159                .map_err(|e| SetupError::ConfigError(e.to_string()))?;
160
161        // Create admin key if requested
162        let admin_key = if self.create_admin_key {
163            let key = auth_manager
164                .create_api_key(
165                    self.admin_key_name,
166                    Role::Admin,
167                    None,
168                    self.admin_ip_whitelist,
169                )
170                .await
171                .map_err(|e| SetupError::KeyGenerationError(e.to_string()))?;
172            Some(key)
173        } else {
174            None
175        };
176
177        Ok(SetupResult {
178            master_key,
179            storage_config,
180            admin_key,
181            auth_manager,
182        })
183    }
184}
185
186/// Setup result containing initialized components
187pub struct SetupResult {
188    /// Generated or provided master key
189    pub master_key: String,
190    /// Storage configuration used
191    pub storage_config: StorageConfig,
192    /// Admin API key (if created)
193    pub admin_key: Option<crate::models::ApiKey>,
194    /// Initialized authentication manager
195    pub auth_manager: AuthenticationManager,
196}
197
198impl SetupResult {
199    /// Generate a configuration summary
200    pub fn config_summary(&self) -> String {
201        let storage_desc = match &self.storage_config {
202            StorageConfig::File { path, .. } => format!("File: {}", path.display()),
203            StorageConfig::Environment { .. } => "Environment Variables".to_string(),
204            _ => "Custom".to_string(),
205        };
206
207        let mut summary = format!(
208            r##"# MCP Authentication Framework Configuration
209
210## Master Key
211export PULSEENGINE_MCP_MASTER_KEY={}
212
213## Storage Backend
214{}
215"##,
216            self.master_key, storage_desc,
217        );
218
219        if let Some(key) = &self.admin_key {
220            summary.push_str(&format!(
221                r#"
222## Admin API Key
223ID: {}
224Name: {}
225Key: {}
226Role: Admin
227Created: {}
228"#,
229                key.id,
230                key.name,
231                key.key,
232                key.created_at.format("%Y-%m-%d %H:%M:%S UTC"),
233            ));
234        }
235
236        summary
237    }
238
239    /// Save configuration to file
240    pub fn save_config(&self, path: &Path) -> Result<(), SetupError> {
241        std::fs::write(path, self.config_summary())
242            .map_err(|e| SetupError::ConfigError(format!("Failed to save config: {}", e)))
243    }
244}
245
246/// Generate a new master encryption key
247fn generate_master_key() -> Result<String, SetupError> {
248    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
249    use rand::Rng;
250
251    let mut key = [0u8; 32];
252    rand::thread_rng().fill(&mut key);
253    Ok(URL_SAFE_NO_PAD.encode(&key))
254}
255
256/// Create default storage configuration
257fn create_default_storage_config() -> StorageConfig {
258    let path = dirs::home_dir()
259        .unwrap_or_else(|| PathBuf::from("."))
260        .join(".pulseengine")
261        .join("mcp-auth")
262        .join("keys.enc");
263
264    StorageConfig::File {
265        path,
266        file_permissions: 0o600,
267        dir_permissions: 0o700,
268        require_secure_filesystem: true,
269        enable_filesystem_monitoring: false,
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_setup_builder() {
279        let builder = SetupBuilder::new()
280            .with_master_key("test-key".to_string())
281            .with_default_storage()
282            .skip_admin_key();
283
284        assert!(builder.master_key.is_some());
285        assert!(builder.storage_config.is_some());
286        assert!(!builder.create_admin_key);
287    }
288
289    #[test]
290    fn test_generate_master_key() {
291        let key1 = generate_master_key().unwrap();
292        let key2 = generate_master_key().unwrap();
293
294        // Keys should be different
295        assert_ne!(key1, key2);
296
297        // Keys should be base64 encoded and proper length
298        assert!(key1.len() > 40);
299        assert!(key2.len() > 40);
300    }
301}