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 pub fn new(base_resource_uri: String, auth_server: String) -> Self {
304 Self {
305 resources: Arc::new(RwLock::new(HashMap::new())),
306 default_auth_server: auth_server,
307 base_resource_uri,
308 }
309 }
310
311 pub async fn register_resource(
313 &self,
314 resource_id: &str,
315 scopes: Vec<String>,
316 documentation: Option<String>,
317 ) -> McpResult<()> {
318 let resource_uri = format!(
319 "{}/{}",
320 self.base_resource_uri.trim_end_matches('/'),
321 resource_id
322 );
323
324 let metadata = ProtectedResourceMetadata {
325 resource: resource_uri.clone(),
326 authorization_server: self.default_auth_server.clone(),
327 scopes_supported: Some(scopes),
328 bearer_methods_supported: Some(vec![
329 BearerTokenMethod::Header, BearerTokenMethod::Body, ]),
332 resource_documentation: documentation,
333 additional_metadata: HashMap::new(),
334 };
335
336 self.resources.write().await.insert(resource_uri, metadata);
337 Ok(())
338 }
339
340 pub async fn get_resource_metadata(
342 &self,
343 resource_uri: &str,
344 ) -> Option<ProtectedResourceMetadata> {
345 self.resources.read().await.get(resource_uri).cloned()
346 }
347
348 pub async fn list_resources(&self) -> Vec<String> {
350 self.resources.read().await.keys().cloned().collect()
351 }
352
353 pub async fn generate_well_known_metadata(&self) -> HashMap<String, ProtectedResourceMetadata> {
355 self.resources.read().await.clone()
356 }
357
358 pub async fn validate_scope_for_resource(
360 &self,
361 resource_uri: &str,
362 token_scopes: &[String],
363 ) -> McpResult<bool> {
364 if let Some(metadata) = self.get_resource_metadata(resource_uri).await {
365 if let Some(required_scopes) = metadata.scopes_supported {
366 let has_required_scope = required_scopes
368 .iter()
369 .any(|scope| token_scopes.contains(scope));
370 Ok(has_required_scope)
371 } else {
372 Ok(true)
374 }
375 } else {
376 Err(McpError::validation(format!(
377 "Unknown resource: {}",
378 resource_uri
379 )))
380 }
381 }
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct ClientRegistrationRequest {
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub redirect_uris: Option<Vec<String>>,
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub response_types: Option<Vec<String>>,
393 #[serde(skip_serializing_if = "Option::is_none")]
395 pub grant_types: Option<Vec<String>>,
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub application_type: Option<ApplicationType>,
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub client_name: Option<String>,
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub client_uri: Option<String>,
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub logo_uri: Option<String>,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub scope: Option<String>,
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub contacts: Option<Vec<String>>,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub tos_uri: Option<String>,
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub policy_uri: Option<String>,
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub software_id: Option<String>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub software_version: Option<String>,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct ClientRegistrationResponse {
431 pub client_id: String,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub client_secret: Option<String>,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub registration_access_token: Option<String>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub registration_client_uri: Option<String>,
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub client_id_issued_at: Option<i64>,
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub client_secret_expires_at: Option<i64>,
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub redirect_uris: Option<Vec<String>>,
451 #[serde(skip_serializing_if = "Option::is_none")]
453 pub response_types: Option<Vec<String>>,
454 #[serde(skip_serializing_if = "Option::is_none")]
456 pub grant_types: Option<Vec<String>>,
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub application_type: Option<ApplicationType>,
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub client_name: Option<String>,
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub scope: Option<String>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470#[serde(rename_all = "lowercase")]
471pub enum ApplicationType {
472 Web,
474 Native,
476}
477
478impl Default for ApplicationType {
479 fn default() -> Self {
480 Self::Web
481 }
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct ClientRegistrationError {
487 pub error: ClientRegistrationErrorCode,
489 #[serde(skip_serializing_if = "Option::is_none")]
491 pub error_description: Option<String>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
496#[serde(rename_all = "snake_case")]
497pub enum ClientRegistrationErrorCode {
498 InvalidRedirectUri,
500 InvalidClientMetadata,
502 InvalidSoftwareStatement,
504 UnapprovedSoftwareStatement,
506}
507
508#[derive(Debug, Clone)]
510pub struct DynamicClientRegistration {
511 registration_endpoint: String,
513 default_application_type: ApplicationType,
515 default_grant_types: Vec<String>,
517 default_response_types: Vec<String>,
519 client: reqwest::Client,
521}
522
523impl DynamicClientRegistration {
524 pub fn new(registration_endpoint: String) -> Self {
526 Self {
527 registration_endpoint,
528 default_application_type: ApplicationType::Web,
529 default_grant_types: vec!["authorization_code".to_string()],
530 default_response_types: vec!["code".to_string()],
531 client: reqwest::Client::new(),
532 }
533 }
534
535 pub async fn register_client(
537 &self,
538 request: ClientRegistrationRequest,
539 ) -> McpResult<ClientRegistrationResponse> {
540 let mut registration_request = request;
542
543 if registration_request.application_type.is_none() {
545 registration_request.application_type = Some(self.default_application_type.clone());
546 }
547 if registration_request.grant_types.is_none() {
548 registration_request.grant_types = Some(self.default_grant_types.clone());
549 }
550 if registration_request.response_types.is_none() {
551 registration_request.response_types = Some(self.default_response_types.clone());
552 }
553
554 let response = self
556 .client
557 .post(&self.registration_endpoint)
558 .header("Content-Type", "application/json")
559 .json(®istration_request)
560 .send()
561 .await
562 .map_err(|e| McpError::validation(format!("Registration request failed: {}", e)))?;
563
564 if response.status().is_success() {
566 let registration_response: ClientRegistrationResponse =
567 response.json().await.map_err(|e| {
568 McpError::validation(format!("Invalid registration response: {}", e))
569 })?;
570 Ok(registration_response)
571 } else {
572 let error_response: ClientRegistrationError = response
574 .json()
575 .await
576 .map_err(|e| McpError::validation(format!("Invalid error response: {}", e)))?;
577 Err(McpError::validation(format!(
578 "Client registration failed: {} - {}",
579 error_response.error as u32,
580 error_response.error_description.unwrap_or_default()
581 )))
582 }
583 }
584
585 pub fn create_mcp_client_request(
587 client_name: &str,
588 redirect_uris: Vec<String>,
589 mcp_server_uri: &str,
590 ) -> ClientRegistrationRequest {
591 ClientRegistrationRequest {
592 redirect_uris: Some(redirect_uris),
593 response_types: Some(vec!["code".to_string()]),
594 grant_types: Some(vec!["authorization_code".to_string()]),
595 application_type: Some(ApplicationType::Web),
596 client_name: Some(format!("MCP Client: {}", client_name)),
597 client_uri: Some(mcp_server_uri.to_string()),
598 scope: Some(
599 "mcp:tools:read mcp:tools:execute mcp:resources:read mcp:prompts:read".to_string(),
600 ),
601 software_id: Some("turbomcp".to_string()),
602 software_version: Some(env!("CARGO_PKG_VERSION").to_string()),
603 logo_uri: None,
604 contacts: None,
605 tos_uri: None,
606 policy_uri: None,
607 }
608 }
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct DeviceAuthorizationResponse {
614 pub device_code: String,
616 pub user_code: String,
618 pub verification_uri: String,
620 pub verification_uri_complete: Option<String>,
622 pub expires_in: u64,
624 pub interval: u64,
626}
627
628#[derive(Debug, Clone)]
630pub struct ProviderConfig {
631 pub provider_type: ProviderType,
633 pub default_scopes: Vec<String>,
635 pub refresh_behavior: RefreshBehavior,
637 pub userinfo_endpoint: Option<String>,
639 pub additional_params: HashMap<String, String>,
641}
642
643#[derive(Debug, Clone, PartialEq)]
645pub enum ProviderType {
646 Google,
648 Microsoft,
650 GitHub,
652 GitLab,
654 Generic,
656 Custom(String),
658}
659
660#[derive(Debug, Clone)]
662pub enum RefreshBehavior {
663 Proactive,
665 Reactive,
667 Custom,
669}