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 {
103 Self::Password {
104 username: username.into(),
105 password: password.into(),
106 }
107 }
108
109 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 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 pub fn oauth_refresh(refresh_token: impl Into<String>) -> Self {
134 Self::OAuthRefresh {
135 refresh_token: refresh_token.into(),
136 }
137 }
138
139 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 pub fn api_key(key: impl Into<String>) -> Self {
154 Self::ApiKey { key: key.into() }
155 }
156
157 pub fn jwt(token: impl Into<String>) -> Self {
159 Self::Jwt {
160 token: token.into(),
161 }
162 }
163
164 pub fn bearer(token: impl Into<String>) -> Self {
166 Self::Bearer {
167 token: token.into(),
168 }
169 }
170
171 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 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 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 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 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 #[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 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 pub fn supports_refresh(&self) -> bool {
260 matches!(
261 self,
262 Self::OAuth { .. } | Self::OAuthRefresh { .. } | Self::OpenIdConnect { .. }
263 )
264 }
265
266 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 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 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
319pub struct CredentialMetadata {
320 pub client_id: Option<String>,
322
323 pub scopes: Vec<String>,
325
326 pub user_agent: Option<String>,
328
329 pub client_ip: Option<String>,
331
332 pub custom: HashMap<String, String>,
334}
335
336impl CredentialMetadata {
337 pub fn new() -> Self {
339 Self::default()
340 }
341
342 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 pub fn scope(mut self, scope: impl Into<String>) -> Self {
350 self.scopes.push(scope.into());
351 self
352 }
353
354 pub fn scopes(mut self, scopes: Vec<String>) -> Self {
356 self.scopes = scopes;
357 self
358 }
359
360 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct AuthRequest {
382 pub credential: Credential,
384
385 pub metadata: CredentialMetadata,
387
388 pub timestamp: chrono::DateTime<chrono::Utc>,
390}
391
392impl AuthRequest {
393 pub fn new(credential: Credential) -> Self {
395 Self {
396 credential,
397 metadata: CredentialMetadata::default(),
398 timestamp: chrono::Utc::now(),
399 }
400 }
401
402 pub fn with_metadata(credential: Credential, metadata: CredentialMetadata) -> Self {
404 Self {
405 credential,
406 metadata,
407 timestamp: chrono::Utc::now(),
408 }
409 }
410
411 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