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