1use base64::Engine;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type", content = "data")]
10pub enum Credential {
11 Password { username: String, password: String },
13
14 OAuth {
16 authorization_code: String,
17 redirect_uri: Option<String>,
18 code_verifier: Option<String>, state: Option<String>,
20 },
21
22 OAuthRefresh { refresh_token: String },
24
25 ApiKey { key: String },
27
28 Jwt { token: String },
30
31 Bearer { token: String },
33
34 Basic { credentials: String },
36
37 Custom {
39 method: String,
40 data: HashMap<String, String>,
41 },
42
43 Mfa {
45 primary_credential: Box<Credential>,
46 mfa_code: String,
47 challenge_id: String,
48 },
49
50 Certificate {
52 certificate: Vec<u8>,
53 private_key: Vec<u8>,
54 passphrase: Option<String>,
55 },
56
57 #[cfg(feature = "saml")]
59 Saml { assertion: String },
60
61 OpenIdConnect {
63 id_token: String,
64 access_token: Option<String>,
65 refresh_token: Option<String>,
66 },
67
68 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 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 #[cfg(feature = "passkeys")]
94 Passkey {
95 credential_id: Vec<u8>,
96 assertion_response: String, },
98}
99
100impl Credential {
101 pub fn password(username: impl Into<String>, password: impl Into<String>) -> Self {
111 Self::Password {
112 username: username.into(),
113 password: password.into(),
114 }
115 }
116
117 pub fn oauth_code(authorization_code: impl Into<String>) -> Self {
122 Self::OAuth {
123 authorization_code: authorization_code.into(),
124 redirect_uri: None,
125 code_verifier: None,
126 state: None,
127 }
128 }
129
130 pub fn oauth_code_with_pkce(
134 authorization_code: impl Into<String>,
135 code_verifier: impl Into<String>,
136 ) -> Self {
137 Self::OAuth {
138 authorization_code: authorization_code.into(),
139 redirect_uri: None,
140 code_verifier: Some(code_verifier.into()),
141 state: None,
142 }
143 }
144
145 pub fn oauth_refresh(refresh_token: impl Into<String>) -> Self {
147 Self::OAuthRefresh {
148 refresh_token: refresh_token.into(),
149 }
150 }
151
152 pub fn device_code(device_code: impl Into<String>, client_id: impl Into<String>) -> Self {
158 Self::DeviceCode {
159 device_code: device_code.into(),
160 client_id: client_id.into(),
161 user_code: None,
162 verification_uri: None,
163 verification_uri_complete: None,
164 expires_in: None,
165 interval: None,
166 }
167 }
168
169 pub fn api_key(key: impl Into<String>) -> Self {
171 Self::ApiKey { key: key.into() }
172 }
173
174 pub fn jwt(token: impl Into<String>) -> Self {
176 Self::Jwt {
177 token: token.into(),
178 }
179 }
180
181 pub fn bearer(token: impl Into<String>) -> Self {
183 Self::Bearer {
184 token: token.into(),
185 }
186 }
187
188 pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
190 let credentials = format!("{}:{}", username.into(), password.into());
191 let encoded = base64::engine::general_purpose::STANDARD.encode(credentials.as_bytes());
192
193 Self::Basic {
194 credentials: encoded,
195 }
196 }
197
198 pub fn custom(method: impl Into<String>, data: HashMap<String, String>) -> Self {
214 Self::Custom {
215 method: method.into(),
216 data,
217 }
218 }
219
220 pub fn mfa(
223 primary_credential: Credential,
224 mfa_code: impl Into<String>,
225 challenge_id: impl Into<String>,
226 ) -> Self {
227 Self::Mfa {
228 primary_credential: Box::new(primary_credential),
229 mfa_code: mfa_code.into(),
230 challenge_id: challenge_id.into(),
231 }
232 }
233
234 pub fn certificate(
239 certificate: Vec<u8>,
240 private_key: Vec<u8>,
241 passphrase: Option<String>,
242 ) -> Self {
243 Self::Certificate {
244 certificate,
245 private_key,
246 passphrase,
247 }
248 }
249
250 pub fn client_cert_from_tls(der_certificate: Vec<u8>) -> Self {
254 Self::Certificate {
255 certificate: der_certificate,
256 private_key: vec![],
257 passphrase: None,
258 }
259 }
260
261 pub fn openid_connect(id_token: impl Into<String>) -> Self {
265 Self::OpenIdConnect {
266 id_token: id_token.into(),
267 access_token: None,
268 refresh_token: None,
269 }
270 }
271
272 #[cfg(feature = "passkeys")]
274 pub fn passkey(credential_id: Vec<u8>, assertion_response: impl Into<String>) -> Self {
275 Self::Passkey {
276 credential_id,
277 assertion_response: assertion_response.into(),
278 }
279 }
280
281 pub fn credential_type(&self) -> &str {
283 match self {
284 Self::Password { .. } => "password",
285 Self::OAuth { .. } => "oauth",
286 Self::OAuthRefresh { .. } => "oauth_refresh",
287 Self::ApiKey { .. } => "api_key",
288 Self::Jwt { .. } => "jwt",
289 Self::Bearer { .. } => "bearer",
290 Self::Basic { .. } => "basic",
291 Self::Custom { method, .. } => method.as_str(),
292 Self::Mfa { .. } => "mfa",
293 Self::Certificate { .. } => "certificate",
294 #[cfg(feature = "saml")]
295 Self::Saml { .. } => "saml",
296 Self::OpenIdConnect { .. } => "openid_connect",
297 Self::DeviceCode { .. } => "device_code",
298 Self::EnhancedDeviceFlow { .. } => "enhanced_device_flow",
299 #[cfg(feature = "passkeys")]
300 Self::Passkey { .. } => "passkey",
301 }
302 }
303
304 pub fn supports_refresh(&self) -> bool {
306 matches!(
307 self,
308 Self::OAuth { .. } | Self::OAuthRefresh { .. } | Self::OpenIdConnect { .. }
309 )
310 }
311
312 pub fn refresh_token(&self) -> Option<&str> {
314 match self {
315 Self::OAuthRefresh { refresh_token } => Some(refresh_token),
316 Self::OpenIdConnect { refresh_token, .. } => refresh_token.as_deref(),
317 _ => None,
318 }
319 }
320
321 pub fn is_sensitive(&self) -> bool {
323 matches!(
324 self,
325 Self::Password { .. }
326 | Self::ApiKey { .. }
327 | Self::Jwt { .. }
328 | Self::Bearer { .. }
329 | Self::Basic { .. }
330 | Self::Certificate { .. }
331 | Self::Mfa { .. }
332 )
333 }
334
335 pub fn safe_display(&self) -> String {
337 match self {
338 Self::Password { username, .. } => {
339 format!("Password(username: {username})")
340 }
341 Self::OAuth { .. } => "OAuth(authorization_code)".to_string(),
342 Self::OAuthRefresh { .. } => "OAuthRefresh(refresh_token)".to_string(),
343 Self::ApiKey { .. } => "ApiKey(****)".to_string(),
344 Self::Jwt { .. } => "Jwt(****)".to_string(),
345 Self::Bearer { .. } => "Bearer(****)".to_string(),
346 Self::Basic { .. } => "Basic(****)".to_string(),
347 Self::Custom { method, .. } => format!("Custom(method: {method})"),
348 Self::Mfa { challenge_id, .. } => {
349 format!("Mfa(challenge_id: {challenge_id})")
350 }
351 Self::Certificate { .. } => "Certificate(****)".to_string(),
352 #[cfg(feature = "saml")]
353 Self::Saml { .. } => "Saml(****)".to_string(),
354 Self::OpenIdConnect { .. } => "OpenIdConnect(id_token)".to_string(),
355 Self::DeviceCode { .. } => "DeviceCode(****)".to_string(),
356 Self::EnhancedDeviceFlow { .. } => "EnhancedDeviceFlow(****)".to_string(),
357 #[cfg(feature = "passkeys")]
358 Self::Passkey { .. } => "Passkey(****)".to_string(),
359 }
360 }
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize, Default)]
376pub struct CredentialMetadata {
377 pub client_id: Option<String>,
379
380 pub scopes: Vec<String>,
382
383 pub user_agent: Option<String>,
385
386 pub client_ip: Option<String>,
388
389 pub custom: HashMap<String, String>,
391}
392
393impl CredentialMetadata {
394 pub fn new() -> Self {
396 Self::default()
397 }
398
399 pub fn client_id(mut self, client_id: impl Into<String>) -> Self {
401 self.client_id = Some(client_id.into());
402 self
403 }
404
405 pub fn scope(mut self, scope: impl Into<String>) -> Self {
407 self.scopes.push(scope.into());
408 self
409 }
410
411 pub fn scopes(mut self, scopes: Vec<String>) -> Self {
413 self.scopes = scopes;
414 self
415 }
416
417 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
419 self.user_agent = Some(user_agent.into());
420 self
421 }
422
423 pub fn client_ip(mut self, client_ip: impl Into<String>) -> Self {
425 self.client_ip = Some(client_ip.into());
426 self
427 }
428
429 pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
431 self.custom.insert(key.into(), value.into());
432 self
433 }
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct AuthRequest {
439 pub credential: Credential,
441
442 pub metadata: CredentialMetadata,
444
445 pub timestamp: chrono::DateTime<chrono::Utc>,
447}
448
449impl AuthRequest {
450 pub fn new(credential: Credential) -> Self {
452 Self {
453 credential,
454 metadata: CredentialMetadata::default(),
455 timestamp: chrono::Utc::now(),
456 }
457 }
458
459 pub fn with_metadata(credential: Credential, metadata: CredentialMetadata) -> Self {
461 Self {
462 credential,
463 metadata,
464 timestamp: chrono::Utc::now(),
465 }
466 }
467
468 pub fn safe_display(&self) -> String {
470 format!(
471 "AuthRequest(credential: {}, client_id: {:?}, timestamp: {})",
472 self.credential.safe_display(),
473 self.metadata.client_id,
474 self.timestamp
475 )
476 }
477}