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 std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", &master_key);
136
137 let storage_config = self
139 .storage_config
140 .unwrap_or_else(|| create_default_storage_config());
141
142 let validation_config = self.validation_config.unwrap_or_default();
144
145 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 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 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
186pub struct SetupResult {
188 pub master_key: String,
190 pub storage_config: StorageConfig,
192 pub admin_key: Option<crate::models::ApiKey>,
194 pub auth_manager: AuthenticationManager,
196}
197
198impl SetupResult {
199 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 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
246fn 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
256fn 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 assert_ne!(key1, key2);
296
297 assert!(key1.len() > 40);
299 assert!(key2.len() > 40);
300 }
301}