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.1 flows
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62pub enum SecurityLevel {
63    /// Standard OAuth 2.1 with PKCE
64    Standard,
65    /// Enhanced security with DPoP token binding
66    Enhanced,
67    /// Maximum security with full DPoP
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.1 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.1 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.1 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.1 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    #[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    /// Register a protected resource (RFC 9728)
313    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, // Primary method
331                BearerTokenMethod::Body,   // For POST requests
332            ]),
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    /// Get metadata for a specific resource
342    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    /// List all registered resources
350    pub async fn list_resources(&self) -> Vec<String> {
351        self.resources.read().await.keys().cloned().collect()
352    }
353
354    /// Generate RFC 9728 compliant metadata for well-known endpoint
355    pub async fn generate_well_known_metadata(&self) -> HashMap<String, ProtectedResourceMetadata> {
356        self.resources.read().await.clone()
357    }
358
359    /// Validate that a token has required scope for resource access
360    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                // Check if token has at least one required scope
368                let has_required_scope = required_scopes
369                    .iter()
370                    .any(|scope| token_scopes.contains(scope));
371                Ok(has_required_scope)
372            } else {
373                // No specific scopes required
374                Ok(true)
375            }
376        } else {
377            Err(McpError::validation(format!(
378                "Unknown resource: {}",
379                resource_uri
380            )))
381        }
382    }
383}
384
385/// Dynamic Client Registration Request (RFC 7591)
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct ClientRegistrationRequest {
388    /// Client metadata - redirect URIs (REQUIRED for authorization code flow)
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub redirect_uris: Option<Vec<String>>,
391    /// Client metadata - response types
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub response_types: Option<Vec<String>>,
394    /// Client metadata - grant types
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub grant_types: Option<Vec<String>>,
397    /// Application type (web, native)
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub application_type: Option<ApplicationType>,
400    /// Human-readable client name
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub client_name: Option<String>,
403    /// Client URI for information
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub client_uri: Option<String>,
406    /// Logo URI
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub logo_uri: Option<String>,
409    /// Scope string with space-delimited scopes
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub scope: Option<String>,
412    /// Contacts (email addresses)
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub contacts: Option<Vec<String>>,
415    /// Terms of service URI
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub tos_uri: Option<String>,
418    /// Privacy policy URI
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub policy_uri: Option<String>,
421    /// Software ID for client
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub software_id: Option<String>,
424    /// Software version
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub software_version: Option<String>,
427}
428
429/// Dynamic Client Registration Response (RFC 7591)
430#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct ClientRegistrationResponse {
432    /// Unique client identifier (REQUIRED)
433    pub client_id: String,
434    /// Client secret (OPTIONAL - not provided for public clients)
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub client_secret: Option<String>,
437    /// Registration access token for client configuration endpoint
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub registration_access_token: Option<String>,
440    /// Client configuration endpoint
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub registration_client_uri: Option<String>,
443    /// Client ID issued at timestamp
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub client_id_issued_at: Option<i64>,
446    /// Client secret expires at timestamp (REQUIRED if client_secret provided)
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub client_secret_expires_at: Option<i64>,
449    /// Confirmed client metadata - redirect URIs
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub redirect_uris: Option<Vec<String>>,
452    /// Confirmed response types
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub response_types: Option<Vec<String>>,
455    /// Confirmed grant types
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub grant_types: Option<Vec<String>>,
458    /// Confirmed application type
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub application_type: Option<ApplicationType>,
461    /// Confirmed client name
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub client_name: Option<String>,
464    /// Confirmed scope
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub scope: Option<String>,
467}
468
469/// Application type for OAuth client (RFC 7591)
470#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
471#[serde(rename_all = "lowercase")]
472pub enum ApplicationType {
473    /// Web application - runs on web server, can keep secrets
474    Web,
475    /// Native application - mobile/desktop app, cannot keep secrets
476    Native,
477}
478
479impl Default for ApplicationType {
480    fn default() -> Self {
481        Self::Web
482    }
483}
484
485/// Client Registration Error Response (RFC 7591)
486#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct ClientRegistrationError {
488    /// Error code
489    pub error: ClientRegistrationErrorCode,
490    /// Human-readable error description
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub error_description: Option<String>,
493}
494
495/// Client Registration Error Codes (RFC 7591)
496#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
497#[serde(rename_all = "snake_case")]
498pub enum ClientRegistrationErrorCode {
499    /// The value of one or more redirect_uris is invalid
500    InvalidRedirectUri,
501    /// The value of one of the client metadata fields is invalid
502    InvalidClientMetadata,
503    /// The software statement presented is invalid
504    InvalidSoftwareStatement,
505    /// The software statement cannot be checked
506    UnapprovedSoftwareStatement,
507}
508
509/// Dynamic Client Registration Manager for RFC 7591 compliance
510#[derive(Debug, Clone)]
511pub struct DynamicClientRegistration {
512    /// Registration endpoint URL
513    registration_endpoint: String,
514    /// Default application type for new registrations
515    default_application_type: ApplicationType,
516    /// Default grant types
517    default_grant_types: Vec<String>,
518    /// Default response types
519    default_response_types: Vec<String>,
520    /// HTTP client for registration requests
521    client: reqwest::Client,
522}
523
524impl DynamicClientRegistration {
525    /// Create a new dynamic client registration manager
526    #[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    /// Register a new OAuth client dynamically (RFC 7591)
538    pub async fn register_client(
539        &self,
540        request: ClientRegistrationRequest,
541    ) -> McpResult<ClientRegistrationResponse> {
542        // Prepare registration request with defaults
543        let mut registration_request = request;
544
545        // Apply defaults if not specified
546        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        // Send registration request
557        let response = self
558            .client
559            .post(&self.registration_endpoint)
560            .header("Content-Type", "application/json")
561            .json(&registration_request)
562            .send()
563            .await
564            .map_err(|e| McpError::validation(format!("Registration request failed: {}", e)))?;
565
566        // Handle response
567        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            // Parse error response
575            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    /// Create a default MCP client registration request
588    #[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/// Device authorization response for CLI/IoT flows
615#[derive(Debug, Clone, Serialize, Deserialize)]
616pub struct DeviceAuthorizationResponse {
617    /// Device verification code
618    pub device_code: String,
619    /// User-friendly verification code
620    pub user_code: String,
621    /// Verification URI
622    pub verification_uri: String,
623    /// Complete verification URI (optional)
624    pub verification_uri_complete: Option<String>,
625    /// Expires in seconds
626    pub expires_in: u64,
627    /// Polling interval in seconds
628    pub interval: u64,
629}
630
631/// Provider-specific configuration for handling OAuth quirks
632#[derive(Debug, Clone)]
633pub struct ProviderConfig {
634    /// Provider type (Google, Microsoft, GitHub, etc.)
635    pub provider_type: ProviderType,
636    /// Custom scopes required by provider
637    pub default_scopes: Vec<String>,
638    /// Provider-specific token refresh behavior
639    pub refresh_behavior: RefreshBehavior,
640    /// Custom userinfo endpoint
641    pub userinfo_endpoint: Option<String>,
642    /// Additional provider-specific parameters
643    pub additional_params: HashMap<String, String>,
644}
645
646/// OAuth2 provider types with built-in configurations
647#[derive(Debug, Clone, PartialEq)]
648pub enum ProviderType {
649    /// Google OAuth2 provider
650    Google,
651    /// Microsoft/Azure OAuth2 provider
652    Microsoft,
653    /// GitHub OAuth2 provider
654    GitHub,
655    /// GitLab OAuth2 provider
656    GitLab,
657    /// Generic OAuth2 provider with standard scopes
658    Generic,
659    /// Custom provider with custom configuration
660    Custom(String),
661}
662
663/// Token refresh behavior strategies
664#[derive(Debug, Clone)]
665pub enum RefreshBehavior {
666    /// Always refresh tokens before expiration
667    Proactive,
668    /// Only refresh when token is actually expired
669    Reactive,
670    /// Custom refresh logic
671    Custom,
672}