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