turbomcp_auth/
types.rs

1//! Core Authentication Types
2//!
3//! This module contains core types used throughout the TurboMCP authentication system.
4
5use std::collections::HashMap;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use async_trait::async_trait;
9use oauth2::RefreshToken;
10use serde::{Deserialize, Serialize};
11
12use turbomcp_protocol::{Error as McpError, Result as McpResult};
13
14use super::config::AuthProviderType;
15
16/// Authentication context (LEGACY - use `context::AuthContext` instead)
17///
18/// NOTE: This is the legacy AuthContext type. New code should use
19/// `crate::context::AuthContext` (the unified canonical type).
20///
21/// This type will be removed in version 3.0.0. Use the unified `context::AuthContext` instead.
22/// The `to_unified()` method can help with migration.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[deprecated(
25    since = "2.0.5",
26    note = "Use context::AuthContext instead. This type is legacy and will be removed in 3.0.0"
27)]
28pub struct AuthContext {
29    /// User ID
30    pub user_id: String,
31    /// User information
32    pub user: UserInfo,
33    /// User roles
34    pub roles: Vec<String>,
35    /// User permissions
36    pub permissions: Vec<String>,
37    /// Request ID for replay protection (MCP compliant - NOT session-based)
38    ///
39    /// Per MCP specification, authentication is stateless. This field is for
40    /// request-level binding (DPoP nonces, one-time tokens), not session management.
41    pub request_id: String,
42    /// Token information
43    pub token: Option<TokenInfo>,
44    /// Authentication provider used
45    pub provider: String,
46    /// Authentication timestamp
47    pub authenticated_at: SystemTime,
48    /// Token expiry time
49    pub expires_at: Option<SystemTime>,
50    /// Additional metadata
51    pub metadata: HashMap<String, serde_json::Value>,
52}
53
54#[allow(deprecated)]
55impl AuthContext {
56    /// Convert legacy types::AuthContext to unified context::AuthContext
57    pub fn to_unified(&self) -> crate::context::AuthContext {
58        crate::context::AuthContext {
59            sub: self.user_id.clone(),
60            iss: None, // Not present in legacy type
61            aud: None, // Not present in legacy type
62            exp: self
63                .expires_at
64                .and_then(|t| t.duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs())),
65            iat: self
66                .authenticated_at
67                .duration_since(UNIX_EPOCH)
68                .ok()
69                .map(|d| d.as_secs()),
70            nbf: None, // Not present in legacy type
71            jti: None, // Not present in legacy type
72            user: self.user.clone(),
73            roles: self.roles.clone(),
74            permissions: self.permissions.clone(),
75            scopes: Vec::new(), // Not present in legacy type
76            request_id: Some(self.request_id.clone()),
77            authenticated_at: self.authenticated_at,
78            expires_at: self.expires_at,
79            token: self.token.clone(),
80            provider: self.provider.clone(),
81            #[cfg(feature = "dpop")]
82            dpop_jkt: None, // Not present in legacy type
83            metadata: self.metadata.clone(),
84        }
85    }
86}
87
88/// User information
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct UserInfo {
91    /// User ID
92    pub id: String,
93    /// Username
94    pub username: String,
95    /// Email address
96    pub email: Option<String>,
97    /// Display name
98    pub display_name: Option<String>,
99    /// Avatar URL
100    pub avatar_url: Option<String>,
101    /// User metadata
102    pub metadata: HashMap<String, serde_json::Value>,
103}
104
105/// Token information
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct TokenInfo {
108    /// Access token
109    pub access_token: String,
110    /// Token type (Bearer, etc.)
111    pub token_type: String,
112    /// Refresh token
113    pub refresh_token: Option<String>,
114    /// Token expiry in seconds
115    pub expires_in: Option<u64>,
116    /// Token scope
117    pub scope: Option<String>,
118}
119
120/// Authentication provider trait
121#[async_trait]
122pub trait AuthProvider: Send + Sync + std::fmt::Debug {
123    /// Provider name
124    fn name(&self) -> &str;
125
126    /// Provider type
127    fn provider_type(&self) -> AuthProviderType;
128
129    /// Authenticate user with credentials
130    async fn authenticate(
131        &self,
132        credentials: AuthCredentials,
133    ) -> McpResult<crate::context::AuthContext>;
134
135    /// Validate existing token/session
136    async fn validate_token(&self, token: &str) -> McpResult<crate::context::AuthContext>;
137
138    /// Refresh access token
139    async fn refresh_token(&self, refresh_token: &str) -> McpResult<TokenInfo>;
140
141    /// Revoke token/session
142    async fn revoke_token(&self, token: &str) -> McpResult<()>;
143
144    /// Get user information
145    async fn get_user_info(&self, token: &str) -> McpResult<UserInfo>;
146}
147
148/// Authentication credentials
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub enum AuthCredentials {
151    /// Username and password
152    UsernamePassword {
153        /// Username
154        username: String,
155        /// Password
156        password: String,
157    },
158    /// API key
159    ApiKey {
160        /// API key
161        key: String,
162    },
163    /// OAuth 2.1 authorization code
164    OAuth2Code {
165        /// Authorization code
166        code: String,
167        /// State parameter
168        state: String,
169    },
170    /// JWT token
171    JwtToken {
172        /// JWT token
173        token: String,
174    },
175    /// Custom credentials
176    Custom {
177        /// Custom credential data
178        data: HashMap<String, serde_json::Value>,
179    },
180}
181
182/// Secure token storage abstraction
183#[async_trait]
184pub trait TokenStorage: Send + Sync + std::fmt::Debug {
185    /// Store access token securely
186    async fn store_access_token(&self, user_id: &str, token: &AccessToken) -> McpResult<()>;
187
188    /// Retrieve access token
189    async fn get_access_token(&self, user_id: &str) -> McpResult<Option<AccessToken>>;
190
191    /// Store refresh token securely (encrypted at rest)
192    async fn store_refresh_token(&self, user_id: &str, token: &RefreshToken) -> McpResult<()>;
193
194    /// Retrieve refresh token
195    async fn get_refresh_token(&self, user_id: &str) -> McpResult<Option<RefreshToken>>;
196
197    /// Remove all tokens for user (logout)
198    async fn revoke_tokens(&self, user_id: &str) -> McpResult<()>;
199
200    /// List all users with stored tokens (for admin)
201    async fn list_users(&self) -> McpResult<Vec<String>>;
202}
203
204/// Secure access token with metadata
205#[derive(Debug, Clone)]
206pub struct AccessToken {
207    /// The actual token
208    pub(crate) token: String,
209    /// Token expiration time
210    pub(crate) expires_at: Option<SystemTime>,
211    /// Token scopes
212    pub(crate) scopes: Vec<String>,
213    /// Provider metadata
214    pub(crate) metadata: HashMap<String, serde_json::Value>,
215}
216
217impl AccessToken {
218    /// Create a new access token
219    #[must_use]
220    pub fn new(
221        token: String,
222        expires_at: Option<SystemTime>,
223        scopes: Vec<String>,
224        metadata: HashMap<String, serde_json::Value>,
225    ) -> Self {
226        Self {
227            token,
228            expires_at,
229            scopes,
230            metadata,
231        }
232    }
233
234    /// Get the token value
235    #[must_use]
236    pub fn token(&self) -> &str {
237        &self.token
238    }
239
240    /// Get the token expiration time
241    #[must_use]
242    pub fn expires_at(&self) -> Option<SystemTime> {
243        self.expires_at
244    }
245
246    /// Get the token scopes
247    #[must_use]
248    pub fn scopes(&self) -> &[String] {
249        &self.scopes
250    }
251
252    /// Get the token metadata
253    #[must_use]
254    pub fn metadata(&self) -> &HashMap<String, serde_json::Value> {
255        &self.metadata
256    }
257}
258
259/// Authentication middleware trait
260#[async_trait]
261pub trait AuthMiddleware: Send + Sync {
262    /// Extract authentication token from request
263    async fn extract_token(&self, headers: &HashMap<String, String>) -> Option<String>;
264
265    /// Handle authentication failure
266    async fn handle_auth_failure(&self, error: McpError) -> McpResult<()>;
267}
268
269/// Default authentication middleware
270#[derive(Debug, Clone)]
271pub struct DefaultAuthMiddleware;
272
273#[async_trait]
274impl AuthMiddleware for DefaultAuthMiddleware {
275    async fn extract_token(&self, headers: &HashMap<String, String>) -> Option<String> {
276        // Try Authorization header first
277        if let Some(auth_header) = headers
278            .get("authorization")
279            .or_else(|| headers.get("Authorization"))
280        {
281            if let Some(token) = auth_header.strip_prefix("Bearer ") {
282                return Some(token.to_string());
283            }
284            if let Some(token) = auth_header.strip_prefix("ApiKey ") {
285                return Some(token.to_string());
286            }
287        }
288
289        // Try X-API-Key header
290        if let Some(api_key) = headers
291            .get("x-api-key")
292            .or_else(|| headers.get("X-API-Key"))
293        {
294            return Some(api_key.clone());
295        }
296
297        None
298    }
299
300    async fn handle_auth_failure(&self, error: McpError) -> McpResult<()> {
301        tracing::warn!("Authentication failed: {}", error);
302        Err(Box::new(error))
303    }
304}