auth_framework/authentication/
credentials.rs

1//! Credential types for various authentication methods.
2
3use base64::Engine;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Represents different types of credentials that can be used for authentication.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type", content = "data")]
10pub enum Credential {
11    /// Username and password credentials
12    Password { username: String, password: String },
13
14    /// OAuth authorization code flow
15    OAuth {
16        authorization_code: String,
17        redirect_uri: Option<String>,
18        code_verifier: Option<String>, // For PKCE
19        state: Option<String>,
20    },
21
22    /// OAuth refresh token
23    OAuthRefresh { refresh_token: String },
24
25    /// API key authentication
26    ApiKey { key: String },
27
28    /// JSON Web Token
29    Jwt { token: String },
30
31    /// Bearer token (generic)
32    Bearer { token: String },
33
34    /// Basic authentication (username:password base64 encoded)
35    Basic { credentials: String },
36
37    /// Custom authentication with flexible key-value pairs
38    Custom {
39        method: String,
40        data: HashMap<String, String>,
41    },
42
43    /// Multi-factor authentication token
44    Mfa {
45        primary_credential: Box<Credential>,
46        mfa_code: String,
47        challenge_id: String,
48    },
49
50    /// Certificate-based authentication
51    Certificate {
52        certificate: Vec<u8>,
53        private_key: Vec<u8>,
54        passphrase: Option<String>,
55    },
56
57    /// SAML assertion with secure XML signature validation
58    #[cfg(feature = "saml")]
59    Saml { assertion: String },
60
61    /// OpenID Connect ID token
62    OpenIdConnect {
63        id_token: String,
64        access_token: Option<String>,
65        refresh_token: Option<String>,
66    },
67
68    /// Device flow credentials (device code polling)
69    DeviceCode {
70        device_code: String,
71        client_id: String,
72        user_code: Option<String>,
73        verification_uri: Option<String>,
74        verification_uri_complete: Option<String>,
75        expires_in: Option<u64>,
76        interval: Option<u64>,
77    },
78
79    /// Enhanced device flow credentials with full OAuth device flow support
80    EnhancedDeviceFlow {
81        device_code: String,
82        user_code: String,
83        verification_uri: String,
84        verification_uri_complete: Option<String>,
85        expires_in: u64,
86        interval: u64,
87        client_id: String,
88        client_secret: Option<String>,
89        scopes: Vec<String>,
90    },
91
92    /// WebAuthn/FIDO2 Passkey authentication using pure Rust implementation
93    #[cfg(feature = "passkeys")]
94    Passkey {
95        credential_id: Vec<u8>,
96        assertion_response: String, // JSON-serialized AuthenticatorAssertionResponse
97    },
98}
99
100impl Credential {
101    /// Create password credentials
102    pub fn password(username: impl Into<String>, password: impl Into<String>) -> Self {
103        Self::Password {
104            username: username.into(),
105            password: password.into(),
106        }
107    }
108
109    /// Create OAuth authorization code credentials
110    pub fn oauth_code(authorization_code: impl Into<String>) -> Self {
111        Self::OAuth {
112            authorization_code: authorization_code.into(),
113            redirect_uri: None,
114            code_verifier: None,
115            state: None,
116        }
117    }
118
119    /// Create OAuth authorization code credentials with PKCE
120    pub fn oauth_code_with_pkce(
121        authorization_code: impl Into<String>,
122        code_verifier: impl Into<String>,
123    ) -> Self {
124        Self::OAuth {
125            authorization_code: authorization_code.into(),
126            redirect_uri: None,
127            code_verifier: Some(code_verifier.into()),
128            state: None,
129        }
130    }
131
132    /// Create OAuth refresh token credentials
133    pub fn oauth_refresh(refresh_token: impl Into<String>) -> Self {
134        Self::OAuthRefresh {
135            refresh_token: refresh_token.into(),
136        }
137    }
138
139    /// Create device code credentials for device flow
140    pub fn device_code(device_code: impl Into<String>, client_id: impl Into<String>) -> Self {
141        Self::DeviceCode {
142            device_code: device_code.into(),
143            client_id: client_id.into(),
144            user_code: None,
145            verification_uri: None,
146            verification_uri_complete: None,
147            expires_in: None,
148            interval: None,
149        }
150    }
151
152    /// Create API key credentials
153    pub fn api_key(key: impl Into<String>) -> Self {
154        Self::ApiKey { key: key.into() }
155    }
156
157    /// Create JWT credentials
158    pub fn jwt(token: impl Into<String>) -> Self {
159        Self::Jwt {
160            token: token.into(),
161        }
162    }
163
164    /// Create bearer token credentials
165    pub fn bearer(token: impl Into<String>) -> Self {
166        Self::Bearer {
167            token: token.into(),
168        }
169    }
170
171    /// Create basic authentication credentials
172    pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
173        let credentials = format!("{}:{}", username.into(), password.into());
174        let encoded = base64::engine::general_purpose::STANDARD.encode(credentials.as_bytes());
175
176        Self::Basic {
177            credentials: encoded,
178        }
179    }
180
181    /// Create custom credentials
182    pub fn custom(method: impl Into<String>, data: HashMap<String, String>) -> Self {
183        Self::Custom {
184            method: method.into(),
185            data,
186        }
187    }
188
189    /// Create MFA credentials
190    pub fn mfa(
191        primary_credential: Credential,
192        mfa_code: impl Into<String>,
193        challenge_id: impl Into<String>,
194    ) -> Self {
195        Self::Mfa {
196            primary_credential: Box::new(primary_credential),
197            mfa_code: mfa_code.into(),
198            challenge_id: challenge_id.into(),
199        }
200    }
201
202    /// Create certificate credentials
203    pub fn certificate(
204        certificate: Vec<u8>,
205        private_key: Vec<u8>,
206        passphrase: Option<String>,
207    ) -> Self {
208        Self::Certificate {
209            certificate,
210            private_key,
211            passphrase,
212        }
213    }
214
215    /// Create SAML assertion credentials - REMOVED for security
216    ///
217    /// Create OpenID Connect credentials
218    pub fn openid_connect(id_token: impl Into<String>) -> Self {
219        Self::OpenIdConnect {
220            id_token: id_token.into(),
221            access_token: None,
222            refresh_token: None,
223        }
224    }
225
226    /// Create passkey credentials (WebAuthn/FIDO2)
227    #[cfg(feature = "passkeys")]
228    pub fn passkey(credential_id: Vec<u8>, assertion_response: impl Into<String>) -> Self {
229        Self::Passkey {
230            credential_id,
231            assertion_response: assertion_response.into(),
232        }
233    }
234
235    /// Get the credential type as a string
236    pub fn credential_type(&self) -> &str {
237        match self {
238            Self::Password { .. } => "password",
239            Self::OAuth { .. } => "oauth",
240            Self::OAuthRefresh { .. } => "oauth_refresh",
241            Self::ApiKey { .. } => "api_key",
242            Self::Jwt { .. } => "jwt",
243            Self::Bearer { .. } => "bearer",
244            Self::Basic { .. } => "basic",
245            Self::Custom { method, .. } => method.as_str(),
246            Self::Mfa { .. } => "mfa",
247            Self::Certificate { .. } => "certificate",
248            #[cfg(feature = "saml")]
249            Self::Saml { .. } => "saml",
250            Self::OpenIdConnect { .. } => "openid_connect",
251            Self::DeviceCode { .. } => "device_code",
252            Self::EnhancedDeviceFlow { .. } => "enhanced_device_flow",
253            #[cfg(feature = "passkeys")]
254            Self::Passkey { .. } => "passkey",
255        }
256    }
257
258    /// Check if this credential supports refresh
259    pub fn supports_refresh(&self) -> bool {
260        matches!(
261            self,
262            Self::OAuth { .. } | Self::OAuthRefresh { .. } | Self::OpenIdConnect { .. }
263        )
264    }
265
266    /// Extract refresh token if available
267    pub fn refresh_token(&self) -> Option<&str> {
268        match self {
269            Self::OAuthRefresh { refresh_token } => Some(refresh_token),
270            Self::OpenIdConnect { refresh_token, .. } => refresh_token.as_deref(),
271            _ => None,
272        }
273    }
274
275    /// Check if this credential is sensitive and should be masked in logs
276    pub fn is_sensitive(&self) -> bool {
277        matches!(
278            self,
279            Self::Password { .. }
280                | Self::ApiKey { .. }
281                | Self::Jwt { .. }
282                | Self::Bearer { .. }
283                | Self::Basic { .. }
284                | Self::Certificate { .. }
285                | Self::Mfa { .. }
286        )
287    }
288
289    /// Get a safe representation for logging (masks sensitive data)
290    pub fn safe_display(&self) -> String {
291        match self {
292            Self::Password { username, .. } => {
293                format!("Password(username: {username})")
294            }
295            Self::OAuth { .. } => "OAuth(authorization_code)".to_string(),
296            Self::OAuthRefresh { .. } => "OAuthRefresh(refresh_token)".to_string(),
297            Self::ApiKey { .. } => "ApiKey(****)".to_string(),
298            Self::Jwt { .. } => "Jwt(****)".to_string(),
299            Self::Bearer { .. } => "Bearer(****)".to_string(),
300            Self::Basic { .. } => "Basic(****)".to_string(),
301            Self::Custom { method, .. } => format!("Custom(method: {method})"),
302            Self::Mfa { challenge_id, .. } => {
303                format!("Mfa(challenge_id: {challenge_id})")
304            }
305            Self::Certificate { .. } => "Certificate(****)".to_string(),
306            #[cfg(feature = "saml")]
307            Self::Saml { .. } => "Saml(****)".to_string(),
308            Self::OpenIdConnect { .. } => "OpenIdConnect(id_token)".to_string(),
309            Self::DeviceCode { .. } => "DeviceCode(****)".to_string(),
310            Self::EnhancedDeviceFlow { .. } => "EnhancedDeviceFlow(****)".to_string(),
311            #[cfg(feature = "passkeys")]
312            Self::Passkey { .. } => "Passkey(****)".to_string(),
313        }
314    }
315}
316
317/// Additional credential metadata that can be attached to any credential type.
318#[derive(Debug, Clone, Serialize, Deserialize, Default)]
319pub struct CredentialMetadata {
320    /// Client identifier (for OAuth flows)
321    pub client_id: Option<String>,
322
323    /// Requested scopes
324    pub scopes: Vec<String>,
325
326    /// User agent string
327    pub user_agent: Option<String>,
328
329    /// IP address of the client
330    pub client_ip: Option<String>,
331
332    /// Additional custom metadata
333    pub custom: HashMap<String, String>,
334}
335
336impl CredentialMetadata {
337    /// Create new credential metadata
338    pub fn new() -> Self {
339        Self::default()
340    }
341
342    /// Set the client ID
343    pub fn client_id(mut self, client_id: impl Into<String>) -> Self {
344        self.client_id = Some(client_id.into());
345        self
346    }
347
348    /// Add a scope
349    pub fn scope(mut self, scope: impl Into<String>) -> Self {
350        self.scopes.push(scope.into());
351        self
352    }
353
354    /// Set multiple scopes
355    pub fn scopes(mut self, scopes: Vec<String>) -> Self {
356        self.scopes = scopes;
357        self
358    }
359
360    /// Set the user agent
361    pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
362        self.user_agent = Some(user_agent.into());
363        self
364    }
365
366    /// Set the client IP
367    pub fn client_ip(mut self, client_ip: impl Into<String>) -> Self {
368        self.client_ip = Some(client_ip.into());
369        self
370    }
371
372    /// Add custom metadata
373    pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
374        self.custom.insert(key.into(), value.into());
375        self
376    }
377}
378
379/// A complete authentication request with credentials and metadata.
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct AuthRequest {
382    /// The credentials to authenticate with
383    pub credential: Credential,
384
385    /// Additional metadata
386    pub metadata: CredentialMetadata,
387
388    /// Timestamp of the request
389    pub timestamp: chrono::DateTime<chrono::Utc>,
390}
391
392impl AuthRequest {
393    /// Create a new authentication request
394    pub fn new(credential: Credential) -> Self {
395        Self {
396            credential,
397            metadata: CredentialMetadata::default(),
398            timestamp: chrono::Utc::now(),
399        }
400    }
401
402    /// Create a new authentication request with metadata
403    pub fn with_metadata(credential: Credential, metadata: CredentialMetadata) -> Self {
404        Self {
405            credential,
406            metadata,
407            timestamp: chrono::Utc::now(),
408        }
409    }
410
411    /// Get a safe representation for logging
412    pub fn safe_display(&self) -> String {
413        format!(
414            "AuthRequest(credential: {}, client_id: {:?}, timestamp: {})",
415            self.credential.safe_display(),
416            self.metadata.client_id,
417            self.timestamp
418        )
419    }
420}
421
422