pulseengine_mcp_auth/setup/
mod.rs1pub mod validator;
7
8use crate::config::StorageConfig;
9use crate::{AuthConfig, AuthenticationManager, Role, ValidationConfig};
10use std::path::{Path, PathBuf};
11use thiserror::Error;
12
13#[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
32pub 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 pub fn new() -> Self {
58 Self::default()
59 }
60
61 pub fn with_master_key(mut self, key: String) -> Self {
63 self.master_key = Some(key);
64 self
65 }
66
67 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 pub fn with_storage(mut self, config: StorageConfig) -> Self {
82 self.storage_config = Some(config);
83 self
84 }
85
86 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 pub fn with_validation(mut self, config: ValidationConfig) -> Self {
105 self.validation_config = Some(config);
106 self
107 }
108
109 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 pub fn skip_admin_key(mut self) -> Self {
119 self.create_admin_key = false;
120 self
121 }
122
123 pub async fn build(self) -> Result<SetupResult, SetupError> {
125 validator::validate_system()?;
127
128 let master_key = match self.master_key {
130 Some(key) => key,
131 None => generate_master_key()?,
132 };
133
134 unsafe {
137 std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", &master_key);
138 }
139
140 let storage_config = self
142 .storage_config
143 .unwrap_or_else(|| create_default_storage_config());
144
145 let validation_config = self.validation_config.unwrap_or_default();
147
148 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 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 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
189pub struct SetupResult {
191 pub master_key: String,
193 pub storage_config: StorageConfig,
195 pub admin_key: Option<crate::models::ApiKey>,
197 pub auth_manager: AuthenticationManager,
199}
200
201impl SetupResult {
202 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 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
249fn 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
259fn 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 assert_ne!(key1, key2);
299
300 assert!(key1.len() > 40);
302 assert!(key2.len() > 40);
303 }
304}