1use std::collections::HashMap;
6use std::sync::Arc;
7#[cfg(feature = "dpop")]
8use std::time::Duration;
9
10use serde::{Deserialize, Serialize};
11use tokio::sync::RwLock;
12
13use turbomcp_protocol::{Error as McpError, Result as McpResult};
14
15#[cfg(feature = "dpop")]
17use super::dpop::DpopAlgorithm;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct AuthConfig {
22 pub enabled: bool,
24 pub providers: Vec<AuthProviderConfig>,
26 pub session: SessionConfig,
28 pub authorization: AuthorizationConfig,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AuthProviderConfig {
35 pub name: String,
37 pub provider_type: AuthProviderType,
39 pub settings: HashMap<String, serde_json::Value>,
41 pub enabled: bool,
43 pub priority: u32,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49pub enum AuthProviderType {
50 OAuth2,
52 ApiKey,
54 Jwt,
56 Custom,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62pub enum SecurityLevel {
63 Standard,
65 Enhanced,
67 Maximum,
69}
70
71impl Default for SecurityLevel {
72 fn default() -> Self {
73 Self::Standard
74 }
75}
76
77#[cfg(feature = "dpop")]
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DpopConfig {
81 pub key_algorithm: DpopAlgorithm,
83 #[serde(default = "default_proof_lifetime")]
85 pub proof_lifetime: Duration,
86 #[serde(default = "default_clock_skew")]
88 pub clock_skew_tolerance: Duration,
89 #[serde(default)]
91 pub key_storage: DpopKeyStorageConfig,
92}
93
94#[cfg(feature = "dpop")]
95fn default_proof_lifetime() -> Duration {
96 Duration::from_secs(60)
97}
98
99#[cfg(feature = "dpop")]
100fn default_clock_skew() -> Duration {
101 Duration::from_secs(300)
102}
103
104#[cfg(feature = "dpop")]
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub enum DpopKeyStorageConfig {
108 Memory,
110 Redis {
112 url: String,
114 },
115 Hsm {
117 config: serde_json::Value,
119 },
120}
121
122#[cfg(feature = "dpop")]
123impl Default for DpopKeyStorageConfig {
124 fn default() -> Self {
125 Self::Memory
126 }
127}
128
129#[cfg(feature = "dpop")]
130impl Default for DpopConfig {
131 fn default() -> Self {
132 Self {
133 key_algorithm: DpopAlgorithm::ES256,
134 proof_lifetime: default_proof_lifetime(),
135 clock_skew_tolerance: default_clock_skew(),
136 key_storage: DpopKeyStorageConfig::default(),
137 }
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct SessionConfig {
144 pub timeout_seconds: u64,
146 pub secure_cookies: bool,
148 pub cookie_domain: Option<String>,
150 pub storage: SessionStorageType,
152 pub max_sessions_per_user: Option<u32>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub enum SessionStorageType {
159 Memory,
161 Redis,
163 Database,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct AuthorizationConfig {
170 pub rbac_enabled: bool,
172 pub default_roles: Vec<String>,
174 pub inheritance_rules: HashMap<String, Vec<String>>,
176 pub resource_permissions: HashMap<String, Vec<String>>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct OAuth2Config {
183 pub client_id: String,
185 pub client_secret: String,
187 pub auth_url: String,
189 pub token_url: String,
191 pub redirect_uri: String,
193 pub scopes: Vec<String>,
195 pub flow_type: OAuth2FlowType,
197 pub additional_params: HashMap<String, String>,
199 #[serde(default)]
201 pub security_level: SecurityLevel,
202 #[cfg(feature = "dpop")]
204 #[serde(default)]
205 pub dpop_config: Option<DpopConfig>,
206 #[serde(default)]
209 pub mcp_resource_uri: Option<String>,
210 #[serde(default = "default_auto_resource_indicators")]
213 pub auto_resource_indicators: bool,
214}
215
216fn default_auto_resource_indicators() -> bool {
218 true
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
223pub enum OAuth2FlowType {
224 AuthorizationCode,
226 ClientCredentials,
228 DeviceCode,
230 Implicit,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct OAuth2AuthResult {
237 pub auth_url: String,
239 pub state: String,
241 pub code_verifier: Option<String>,
243 pub device_code: Option<String>,
245 pub user_code: Option<String>,
247 pub verification_uri: Option<String>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct ProtectedResourceMetadata {
254 pub resource: String,
256 pub authorization_server: String,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub scopes_supported: Option<Vec<String>>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub bearer_methods_supported: Option<Vec<BearerTokenMethod>>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub resource_documentation: Option<String>,
267 #[serde(flatten)]
269 pub additional_metadata: HashMap<String, serde_json::Value>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
274#[serde(rename_all = "lowercase")]
275pub enum BearerTokenMethod {
276 Header,
278 Query,
280 Body,
282}
283
284impl Default for BearerTokenMethod {
285 fn default() -> Self {
286 Self::Header
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct McpResourceRegistry {
293 resources: Arc<RwLock<HashMap<String, ProtectedResourceMetadata>>>,
295 default_auth_server: String,
297 base_resource_uri: String,
299}
300
301impl McpResourceRegistry {
302 #[must_use]
304 pub fn new(base_resource_uri: String, auth_server: String) -> Self {
305 Self {
306 resources: Arc::new(RwLock::new(HashMap::new())),
307 default_auth_server: auth_server,
308 base_resource_uri,
309 }
310 }
311
312 pub async fn register_resource(
314 &self,
315 resource_id: &str,
316 scopes: Vec<String>,
317 documentation: Option<String>,
318 ) -> McpResult<()> {
319 let resource_uri = format!(
320 "{}/{}",
321 self.base_resource_uri.trim_end_matches('/'),
322 resource_id
323 );
324
325 let metadata = ProtectedResourceMetadata {
326 resource: resource_uri.clone(),
327 authorization_server: self.default_auth_server.clone(),
328 scopes_supported: Some(scopes),
329 bearer_methods_supported: Some(vec![
330 BearerTokenMethod::Header, BearerTokenMethod::Body, ]),
333 resource_documentation: documentation,
334 additional_metadata: HashMap::new(),
335 };
336
337 self.resources.write().await.insert(resource_uri, metadata);
338 Ok(())
339 }
340
341 pub async fn get_resource_metadata(
343 &self,
344 resource_uri: &str,
345 ) -> Option<ProtectedResourceMetadata> {
346 self.resources.read().await.get(resource_uri).cloned()
347 }
348
349 pub async fn list_resources(&self) -> Vec<String> {
351 self.resources.read().await.keys().cloned().collect()
352 }
353
354 pub async fn generate_well_known_metadata(&self) -> HashMap<String, ProtectedResourceMetadata> {
356 self.resources.read().await.clone()
357 }
358
359 pub async fn validate_scope_for_resource(
361 &self,
362 resource_uri: &str,
363 token_scopes: &[String],
364 ) -> McpResult<bool> {
365 if let Some(metadata) = self.get_resource_metadata(resource_uri).await {
366 if let Some(required_scopes) = metadata.scopes_supported {
367 let has_required_scope = required_scopes
369 .iter()
370 .any(|scope| token_scopes.contains(scope));
371 Ok(has_required_scope)
372 } else {
373 Ok(true)
375 }
376 } else {
377 Err(McpError::validation(format!(
378 "Unknown resource: {}",
379 resource_uri
380 )))
381 }
382 }
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct ClientRegistrationRequest {
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub redirect_uris: Option<Vec<String>>,
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub response_types: Option<Vec<String>>,
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub grant_types: Option<Vec<String>>,
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub application_type: Option<ApplicationType>,
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub client_name: Option<String>,
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub client_uri: Option<String>,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub logo_uri: Option<String>,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub scope: Option<String>,
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub contacts: Option<Vec<String>>,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub tos_uri: Option<String>,
418 #[serde(skip_serializing_if = "Option::is_none")]
420 pub policy_uri: Option<String>,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub software_id: Option<String>,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub software_version: Option<String>,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct ClientRegistrationResponse {
432 pub client_id: String,
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub client_secret: Option<String>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub registration_access_token: Option<String>,
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub registration_client_uri: Option<String>,
443 #[serde(skip_serializing_if = "Option::is_none")]
445 pub client_id_issued_at: Option<i64>,
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub client_secret_expires_at: Option<i64>,
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub redirect_uris: Option<Vec<String>>,
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub response_types: Option<Vec<String>>,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 pub grant_types: Option<Vec<String>>,
458 #[serde(skip_serializing_if = "Option::is_none")]
460 pub application_type: Option<ApplicationType>,
461 #[serde(skip_serializing_if = "Option::is_none")]
463 pub client_name: Option<String>,
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub scope: Option<String>,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
471#[serde(rename_all = "lowercase")]
472pub enum ApplicationType {
473 Web,
475 Native,
477}
478
479impl Default for ApplicationType {
480 fn default() -> Self {
481 Self::Web
482 }
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct ClientRegistrationError {
488 pub error: ClientRegistrationErrorCode,
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub error_description: Option<String>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
497#[serde(rename_all = "snake_case")]
498pub enum ClientRegistrationErrorCode {
499 InvalidRedirectUri,
501 InvalidClientMetadata,
503 InvalidSoftwareStatement,
505 UnapprovedSoftwareStatement,
507}
508
509#[derive(Debug, Clone)]
511pub struct DynamicClientRegistration {
512 registration_endpoint: String,
514 default_application_type: ApplicationType,
516 default_grant_types: Vec<String>,
518 default_response_types: Vec<String>,
520 client: reqwest::Client,
522}
523
524impl DynamicClientRegistration {
525 #[must_use]
527 pub fn new(registration_endpoint: String) -> Self {
528 Self {
529 registration_endpoint,
530 default_application_type: ApplicationType::Web,
531 default_grant_types: vec!["authorization_code".to_string()],
532 default_response_types: vec!["code".to_string()],
533 client: reqwest::Client::new(),
534 }
535 }
536
537 pub async fn register_client(
539 &self,
540 request: ClientRegistrationRequest,
541 ) -> McpResult<ClientRegistrationResponse> {
542 let mut registration_request = request;
544
545 if registration_request.application_type.is_none() {
547 registration_request.application_type = Some(self.default_application_type.clone());
548 }
549 if registration_request.grant_types.is_none() {
550 registration_request.grant_types = Some(self.default_grant_types.clone());
551 }
552 if registration_request.response_types.is_none() {
553 registration_request.response_types = Some(self.default_response_types.clone());
554 }
555
556 let response = self
558 .client
559 .post(&self.registration_endpoint)
560 .header("Content-Type", "application/json")
561 .json(®istration_request)
562 .send()
563 .await
564 .map_err(|e| McpError::validation(format!("Registration request failed: {}", e)))?;
565
566 if response.status().is_success() {
568 let registration_response: ClientRegistrationResponse =
569 response.json().await.map_err(|e| {
570 McpError::validation(format!("Invalid registration response: {}", e))
571 })?;
572 Ok(registration_response)
573 } else {
574 let error_response: ClientRegistrationError = response
576 .json()
577 .await
578 .map_err(|e| McpError::validation(format!("Invalid error response: {}", e)))?;
579 Err(McpError::validation(format!(
580 "Client registration failed: {} - {}",
581 error_response.error as u32,
582 error_response.error_description.unwrap_or_default()
583 )))
584 }
585 }
586
587 #[must_use]
589 pub fn create_mcp_client_request(
590 client_name: &str,
591 redirect_uris: Vec<String>,
592 mcp_server_uri: &str,
593 ) -> ClientRegistrationRequest {
594 ClientRegistrationRequest {
595 redirect_uris: Some(redirect_uris),
596 response_types: Some(vec!["code".to_string()]),
597 grant_types: Some(vec!["authorization_code".to_string()]),
598 application_type: Some(ApplicationType::Web),
599 client_name: Some(format!("MCP Client: {}", client_name)),
600 client_uri: Some(mcp_server_uri.to_string()),
601 scope: Some(
602 "mcp:tools:read mcp:tools:execute mcp:resources:read mcp:prompts:read".to_string(),
603 ),
604 software_id: Some("turbomcp".to_string()),
605 software_version: Some(env!("CARGO_PKG_VERSION").to_string()),
606 logo_uri: None,
607 contacts: None,
608 tos_uri: None,
609 policy_uri: None,
610 }
611 }
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
616pub struct DeviceAuthorizationResponse {
617 pub device_code: String,
619 pub user_code: String,
621 pub verification_uri: String,
623 pub verification_uri_complete: Option<String>,
625 pub expires_in: u64,
627 pub interval: u64,
629}
630
631#[derive(Debug, Clone)]
633pub struct ProviderConfig {
634 pub provider_type: ProviderType,
636 pub default_scopes: Vec<String>,
638 pub refresh_behavior: RefreshBehavior,
640 pub userinfo_endpoint: Option<String>,
642 pub additional_params: HashMap<String, String>,
644}
645
646#[derive(Debug, Clone, PartialEq)]
648pub enum ProviderType {
649 Google,
651 Microsoft,
653 GitHub,
655 GitLab,
657 Generic,
659 Custom(String),
661}
662
663#[derive(Debug, Clone)]
665pub enum RefreshBehavior {
666 Proactive,
668 Reactive,
670 Custom,
672}