turbomcp_auth/
config.rs

1//! Authentication Configuration Types
2//!
3//! This module contains all configuration structures for the TurboMCP authentication system.
4
5use 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// DPoP support (feature-gated)
16#[cfg(feature = "dpop")]
17use super::dpop::DpopAlgorithm;
18
19/// Authentication configuration
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct AuthConfig {
22    /// Enable authentication
23    pub enabled: bool,
24    /// Authentication provider configuration
25    pub providers: Vec<AuthProviderConfig>,
26    /// Session configuration
27    pub session: SessionConfig,
28    /// Authorization configuration
29    pub authorization: AuthorizationConfig,
30}
31
32/// Authentication provider configuration
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AuthProviderConfig {
35    /// Provider name
36    pub name: String,
37    /// Provider type
38    pub provider_type: AuthProviderType,
39    /// Provider-specific settings
40    pub settings: HashMap<String, serde_json::Value>,
41    /// Whether this provider is enabled
42    pub enabled: bool,
43    /// Priority (lower number = higher priority)
44    pub priority: u32,
45}
46
47/// Authentication provider types
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49pub enum AuthProviderType {
50    /// OAuth 2.0 provider
51    OAuth2,
52    /// API key provider
53    ApiKey,
54    /// JWT token provider
55    Jwt,
56    /// Custom authentication provider
57    Custom,
58}
59
60/// Security levels for OAuth 2.0 flows
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62pub enum SecurityLevel {
63    /// Standard OAuth 2.0 with PKCE (existing behavior - no breaking changes)
64    Standard,
65    /// Enhanced security with DPoP token binding
66    Enhanced,
67    /// Maximum security with full DPoP + additional features
68    Maximum,
69}
70
71impl Default for SecurityLevel {
72    fn default() -> Self {
73        Self::Standard
74    }
75}
76
77/// DPoP (Demonstration of Proof-of-Possession) configuration
78#[cfg(feature = "dpop")]
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DpopConfig {
81    /// Cryptographic algorithm for DPoP proofs
82    pub key_algorithm: DpopAlgorithm,
83    /// Proof lifetime in seconds (default: 60s per RFC 9449)
84    #[serde(default = "default_proof_lifetime")]
85    pub proof_lifetime: Duration,
86    /// Maximum clock skew tolerance in seconds (default: 300s per RFC 9449)
87    #[serde(default = "default_clock_skew")]
88    pub clock_skew_tolerance: Duration,
89    /// Key storage backend selection
90    #[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/// DPoP key storage configuration
105#[cfg(feature = "dpop")]
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub enum DpopKeyStorageConfig {
108    /// In-memory storage (development)
109    Memory,
110    /// Redis storage (production)
111    Redis {
112        /// Redis connection URL
113        url: String,
114    },
115    /// HSM storage (high security)
116    Hsm {
117        /// HSM configuration parameters
118        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/// Session configuration
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct SessionConfig {
144    /// Session timeout duration in seconds
145    pub timeout_seconds: u64,
146    /// Whether to use secure cookies
147    pub secure_cookies: bool,
148    /// Cookie domain
149    pub cookie_domain: Option<String>,
150    /// Session storage type
151    pub storage: SessionStorageType,
152    /// Maximum concurrent sessions per user
153    pub max_sessions_per_user: Option<u32>,
154}
155
156/// Session storage types
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub enum SessionStorageType {
159    /// In-memory storage
160    Memory,
161    /// Redis storage
162    Redis,
163    /// Database storage
164    Database,
165}
166
167/// Authorization configuration
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct AuthorizationConfig {
170    /// Enable role-based access control
171    pub rbac_enabled: bool,
172    /// Default roles for new users
173    pub default_roles: Vec<String>,
174    /// Permission inheritance rules
175    pub inheritance_rules: HashMap<String, Vec<String>>,
176    /// Resource-based permissions
177    pub resource_permissions: HashMap<String, Vec<String>>,
178}
179
180/// OAuth 2.0 configuration
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct OAuth2Config {
183    /// Client ID
184    pub client_id: String,
185    /// Client secret
186    pub client_secret: String,
187    /// Authorization endpoint
188    pub auth_url: String,
189    /// Token endpoint
190    pub token_url: String,
191    /// Redirect URI
192    pub redirect_uri: String,
193    /// Scopes to request
194    pub scopes: Vec<String>,
195    /// OAuth 2.0 flow type
196    pub flow_type: OAuth2FlowType,
197    /// Additional parameters
198    pub additional_params: HashMap<String, String>,
199    /// Security level for OAuth flow
200    #[serde(default)]
201    pub security_level: SecurityLevel,
202    /// DPoP configuration (when security_level is Enhanced or Maximum)
203    #[cfg(feature = "dpop")]
204    #[serde(default)]
205    pub dpop_config: Option<DpopConfig>,
206    /// MCP server canonical URI for Resource Indicators (RFC 8707)
207    /// This is the target resource server URI that tokens will be bound to
208    #[serde(default)]
209    pub mcp_resource_uri: Option<String>,
210    /// Automatic Resource Indicator mode - when true, resource parameter
211    /// is automatically included in all OAuth flows for MCP compliance
212    #[serde(default = "default_auto_resource_indicators")]
213    pub auto_resource_indicators: bool,
214}
215
216/// Default auto resource indicators setting (enabled for MCP compliance)
217fn default_auto_resource_indicators() -> bool {
218    true
219}
220
221/// OAuth 2.0 flow types
222#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
223pub enum OAuth2FlowType {
224    /// Authorization Code flow
225    AuthorizationCode,
226    /// Client Credentials flow
227    ClientCredentials,
228    /// Device Authorization flow
229    DeviceCode,
230    /// Implicit flow (not recommended)
231    Implicit,
232}
233
234/// OAuth 2.0 authorization result
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct OAuth2AuthResult {
237    /// Authorization URL for user
238    pub auth_url: String,
239    /// State parameter for CSRF protection
240    pub state: String,
241    /// Code verifier for PKCE
242    pub code_verifier: Option<String>,
243    /// Device code (for device flow)
244    pub device_code: Option<String>,
245    /// User code (for device flow)
246    pub user_code: Option<String>,
247    /// Verification URL (for device flow)
248    pub verification_uri: Option<String>,
249}
250
251/// Protected Resource Metadata (RFC 9728) for server-side discovery
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct ProtectedResourceMetadata {
254    /// Resource server identifier (REQUIRED)
255    pub resource: String,
256    /// Authorization server endpoint (REQUIRED)
257    pub authorization_server: String,
258    /// Available scopes for this resource (OPTIONAL)
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub scopes_supported: Option<Vec<String>>,
261    /// Bearer token methods supported (OPTIONAL)
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub bearer_methods_supported: Option<Vec<BearerTokenMethod>>,
264    /// Resource documentation URI (OPTIONAL)
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub resource_documentation: Option<String>,
267    /// Additional metadata (OPTIONAL)
268    #[serde(flatten)]
269    pub additional_metadata: HashMap<String, serde_json::Value>,
270}
271
272/// Bearer token delivery methods (RFC 9728)
273#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
274#[serde(rename_all = "lowercase")]
275pub enum BearerTokenMethod {
276    /// Authorization header (RFC 6750)
277    Header,
278    /// Query parameter (RFC 6750) - discouraged for security
279    Query,
280    /// Request body (RFC 6750) - for POST requests only
281    Body,
282}
283
284impl Default for BearerTokenMethod {
285    fn default() -> Self {
286        Self::Header
287    }
288}
289
290/// MCP Server Resource Registry for RFC 9728 compliance
291#[derive(Debug, Clone)]
292pub struct McpResourceRegistry {
293    /// Map of resource URI to metadata
294    resources: Arc<RwLock<HashMap<String, ProtectedResourceMetadata>>>,
295    /// Default authorization server for new resources
296    default_auth_server: String,
297    /// Base resource URI for this MCP server
298    base_resource_uri: String,
299}
300
301impl McpResourceRegistry {
302    /// Create a new MCP resource registry
303    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    /// Register a protected resource (RFC 9728)
312    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, // Primary method
330                BearerTokenMethod::Body,   // For POST requests
331            ]),
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    /// Get metadata for a specific resource
341    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    /// List all registered resources
349    pub async fn list_resources(&self) -> Vec<String> {
350        self.resources.read().await.keys().cloned().collect()
351    }
352
353    /// Generate RFC 9728 compliant metadata for well-known endpoint
354    pub async fn generate_well_known_metadata(&self) -> HashMap<String, ProtectedResourceMetadata> {
355        self.resources.read().await.clone()
356    }
357
358    /// Validate that a token has required scope for resource access
359    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                // Check if token has at least one required scope
367                let has_required_scope = required_scopes
368                    .iter()
369                    .any(|scope| token_scopes.contains(scope));
370                Ok(has_required_scope)
371            } else {
372                // No specific scopes required
373                Ok(true)
374            }
375        } else {
376            Err(McpError::validation(format!(
377                "Unknown resource: {}",
378                resource_uri
379            )))
380        }
381    }
382}
383
384/// Dynamic Client Registration Request (RFC 7591)
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct ClientRegistrationRequest {
387    /// Client metadata - redirect URIs (REQUIRED for authorization code flow)
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub redirect_uris: Option<Vec<String>>,
390    /// Client metadata - response types
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub response_types: Option<Vec<String>>,
393    /// Client metadata - grant types
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub grant_types: Option<Vec<String>>,
396    /// Application type (web, native)
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub application_type: Option<ApplicationType>,
399    /// Human-readable client name
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub client_name: Option<String>,
402    /// Client URI for information
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub client_uri: Option<String>,
405    /// Logo URI
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub logo_uri: Option<String>,
408    /// Scope string with space-delimited scopes
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub scope: Option<String>,
411    /// Contacts (email addresses)
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub contacts: Option<Vec<String>>,
414    /// Terms of service URI
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub tos_uri: Option<String>,
417    /// Privacy policy URI
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub policy_uri: Option<String>,
420    /// Software ID for client
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub software_id: Option<String>,
423    /// Software version
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub software_version: Option<String>,
426}
427
428/// Dynamic Client Registration Response (RFC 7591)
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct ClientRegistrationResponse {
431    /// Unique client identifier (REQUIRED)
432    pub client_id: String,
433    /// Client secret (OPTIONAL - not provided for public clients)
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub client_secret: Option<String>,
436    /// Registration access token for client configuration endpoint
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub registration_access_token: Option<String>,
439    /// Client configuration endpoint
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub registration_client_uri: Option<String>,
442    /// Client ID issued at timestamp
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub client_id_issued_at: Option<i64>,
445    /// Client secret expires at timestamp (REQUIRED if client_secret provided)
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub client_secret_expires_at: Option<i64>,
448    /// Confirmed client metadata - redirect URIs
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub redirect_uris: Option<Vec<String>>,
451    /// Confirmed response types
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub response_types: Option<Vec<String>>,
454    /// Confirmed grant types
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub grant_types: Option<Vec<String>>,
457    /// Confirmed application type
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub application_type: Option<ApplicationType>,
460    /// Confirmed client name
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub client_name: Option<String>,
463    /// Confirmed scope
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub scope: Option<String>,
466}
467
468/// Application type for OAuth client (RFC 7591)
469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470#[serde(rename_all = "lowercase")]
471pub enum ApplicationType {
472    /// Web application - runs on web server, can keep secrets
473    Web,
474    /// Native application - mobile/desktop app, cannot keep secrets
475    Native,
476}
477
478impl Default for ApplicationType {
479    fn default() -> Self {
480        Self::Web
481    }
482}
483
484/// Client Registration Error Response (RFC 7591)
485#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct ClientRegistrationError {
487    /// Error code
488    pub error: ClientRegistrationErrorCode,
489    /// Human-readable error description
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub error_description: Option<String>,
492}
493
494/// Client Registration Error Codes (RFC 7591)
495#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
496#[serde(rename_all = "snake_case")]
497pub enum ClientRegistrationErrorCode {
498    /// The value of one or more redirect_uris is invalid
499    InvalidRedirectUri,
500    /// The value of one of the client metadata fields is invalid
501    InvalidClientMetadata,
502    /// The software statement presented is invalid
503    InvalidSoftwareStatement,
504    /// The software statement cannot be checked
505    UnapprovedSoftwareStatement,
506}
507
508/// Dynamic Client Registration Manager for RFC 7591 compliance
509#[derive(Debug, Clone)]
510pub struct DynamicClientRegistration {
511    /// Registration endpoint URL
512    registration_endpoint: String,
513    /// Default application type for new registrations
514    default_application_type: ApplicationType,
515    /// Default grant types
516    default_grant_types: Vec<String>,
517    /// Default response types
518    default_response_types: Vec<String>,
519    /// HTTP client for registration requests
520    client: reqwest::Client,
521}
522
523impl DynamicClientRegistration {
524    /// Create a new dynamic client registration manager
525    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    /// Register a new OAuth client dynamically (RFC 7591)
536    pub async fn register_client(
537        &self,
538        request: ClientRegistrationRequest,
539    ) -> McpResult<ClientRegistrationResponse> {
540        // Prepare registration request with defaults
541        let mut registration_request = request;
542
543        // Apply defaults if not specified
544        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        // Send registration request
555        let response = self
556            .client
557            .post(&self.registration_endpoint)
558            .header("Content-Type", "application/json")
559            .json(&registration_request)
560            .send()
561            .await
562            .map_err(|e| McpError::validation(format!("Registration request failed: {}", e)))?;
563
564        // Handle response
565        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            // Parse error response
573            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    /// Create a default MCP client registration request
586    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/// Device authorization response for CLI/IoT flows
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct DeviceAuthorizationResponse {
614    /// Device verification code
615    pub device_code: String,
616    /// User-friendly verification code
617    pub user_code: String,
618    /// Verification URI
619    pub verification_uri: String,
620    /// Complete verification URI (optional)
621    pub verification_uri_complete: Option<String>,
622    /// Expires in seconds
623    pub expires_in: u64,
624    /// Polling interval in seconds
625    pub interval: u64,
626}
627
628/// Provider-specific configuration for handling OAuth quirks
629#[derive(Debug, Clone)]
630pub struct ProviderConfig {
631    /// Provider type (Google, Microsoft, GitHub, etc.)
632    pub provider_type: ProviderType,
633    /// Custom scopes required by provider
634    pub default_scopes: Vec<String>,
635    /// Provider-specific token refresh behavior
636    pub refresh_behavior: RefreshBehavior,
637    /// Custom userinfo endpoint
638    pub userinfo_endpoint: Option<String>,
639    /// Additional provider-specific parameters
640    pub additional_params: HashMap<String, String>,
641}
642
643/// OAuth2 provider types with built-in configurations
644#[derive(Debug, Clone, PartialEq)]
645pub enum ProviderType {
646    /// Google OAuth2 provider
647    Google,
648    /// Microsoft/Azure OAuth2 provider
649    Microsoft,
650    /// GitHub OAuth2 provider
651    GitHub,
652    /// GitLab OAuth2 provider
653    GitLab,
654    /// Generic OAuth2 provider with standard scopes
655    Generic,
656    /// Custom provider with custom configuration
657    Custom(String),
658}
659
660/// Token refresh behavior strategies
661#[derive(Debug, Clone)]
662pub enum RefreshBehavior {
663    /// Always refresh tokens before expiration
664    Proactive,
665    /// Only refresh when token is actually expired
666    Reactive,
667    /// Custom refresh logic
668    Custom,
669}