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        // SAFETY: Setting environment variable during initialization
136        unsafe {
137            std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", &master_key);
138        }
139
140        // Use storage config or default
141        let storage_config = self
142            .storage_config
143            .unwrap_or_else(|| create_default_storage_config());
144
145        // Use validation config or default
146        let validation_config = self.validation_config.unwrap_or_default();
147
148        // Create auth config
149        let auth_config = AuthConfig {
150            enabled: true,
151            storage: storage_config.clone(),
152            cache_size: 1000,
153            session_timeout_secs: validation_config.session_timeout_minutes * 60,
154            max_failed_attempts: validation_config.max_failed_attempts,
155            rate_limit_window_secs: validation_config.failed_attempt_window_minutes * 60,
156        };
157
158        // Initialize authentication manager
159        let auth_manager =
160            AuthenticationManager::new_with_validation(auth_config, validation_config)
161                .await
162                .map_err(|e| SetupError::ConfigError(e.to_string()))?;
163
164        // Create admin key if requested
165        let admin_key = if self.create_admin_key {
166            let key = auth_manager
167                .create_api_key(
168                    self.admin_key_name,
169                    Role::Admin,
170                    None,
171                    self.admin_ip_whitelist,
172                )
173                .await
174                .map_err(|e| SetupError::KeyGenerationError(e.to_string()))?;
175            Some(key)
176        } else {
177            None
178        };
179
180        Ok(SetupResult {
181            master_key,
182            storage_config,
183            admin_key,
184            auth_manager,
185        })
186    }
187}
188
189/// Setup result containing initialized components
190pub struct SetupResult {
191    /// Generated or provided master key
192    pub master_key: String,
193    /// Storage configuration used
194    pub storage_config: StorageConfig,
195    /// Admin API key (if created)
196    pub admin_key: Option<crate::models::ApiKey>,
197    /// Initialized authentication manager
198    pub auth_manager: AuthenticationManager,
199}
200
201impl SetupResult {
202    /// Generate a configuration summary
203    pub fn config_summary(&self) -> String {
204        let storage_desc = match &self.storage_config {
205            StorageConfig::File { path, .. } => format!("File: {}", path.display()),
206            StorageConfig::Environment { .. } => "Environment Variables".to_string(),
207            _ => "Custom".to_string(),
208        };
209
210        let mut summary = format!(
211            r##"# MCP Authentication Framework Configuration
212
213## Master Key
214export PULSEENGINE_MCP_MASTER_KEY={}
215
216## Storage Backend
217{}
218"##,
219            self.master_key, storage_desc,
220        );
221
222        if let Some(key) = &self.admin_key {
223            summary.push_str(&format!(
224                r#"
225## Admin API Key
226ID: {}
227Name: {}
228Key: {}
229Role: Admin
230Created: {}
231"#,
232                key.id,
233                key.name,
234                key.key,
235                key.created_at.format("%Y-%m-%d %H:%M:%S UTC"),
236            ));
237        }
238
239        summary
240    }
241
242    /// Save configuration to file
243    pub fn save_config(&self, path: &Path) -> Result<(), SetupError> {
244        std::fs::write(path, self.config_summary())
245            .map_err(|e| SetupError::ConfigError(format!("Failed to save config: {}", e)))
246    }
247}
248
249/// Generate a new master encryption key
250fn generate_master_key() -> Result<String, SetupError> {
251    use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
252    use rand::Rng;
253
254    let mut key = [0u8; 32];
255    rand::thread_rng().fill(&mut key);
256    Ok(URL_SAFE_NO_PAD.encode(&key))
257}
258
259/// Create default storage configuration
260fn create_default_storage_config() -> StorageConfig {
261    let path = dirs::home_dir()
262        .unwrap_or_else(|| PathBuf::from("."))
263        .join(".pulseengine")
264        .join("mcp-auth")
265        .join("keys.enc");
266
267    StorageConfig::File {
268        path,
269        file_permissions: 0o600,
270        dir_permissions: 0o700,
271        require_secure_filesystem: true,
272        enable_filesystem_monitoring: false,
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_setup_builder() {
282        let builder = SetupBuilder::new()
283            .with_master_key("test-key".to_string())
284            .with_default_storage()
285            .skip_admin_key();
286
287        assert!(builder.master_key.is_some());
288        assert!(builder.storage_config.is_some());
289        assert!(!builder.create_admin_key);
290    }
291
292    #[test]
293    fn test_generate_master_key() {
294        let key1 = generate_master_key().unwrap();
295        let key2 = generate_master_key().unwrap();
296
297        // Keys should be different
298        assert_ne!(key1, key2);
299
300        // Keys should be base64 encoded and proper length
301        assert!(key1.len() > 40);
302        assert!(key2.len() > 40);
303    }
304}