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 {
13 username: String,
14 password: String,
15 },
16
17 OAuth {
19 authorization_code: String,
20 redirect_uri: Option<String>,
21 code_verifier: Option<String>, state: Option<String>,
23 },
24
25 OAuthRefresh {
27 refresh_token: String,
28 },
29
30 ApiKey {
32 key: String,
33 },
34
35 Jwt {
37 token: String,
38 },
39
40 Bearer {
42 token: String,
43 },
44
45 Basic {
47 credentials: String,
48 },
49
50 Custom {
52 method: String,
53 data: HashMap<String, String>,
54 },
55
56 Mfa {
58 primary_credential: Box<Credential>,
59 mfa_code: String,
60 challenge_id: String,
61 },
62
63 Certificate {
65 certificate: Vec<u8>,
66 private_key: Vec<u8>,
67 passphrase: Option<String>,
68 },
69
70 Saml {
72 assertion: String,
73 relay_state: Option<String>,
74 },
75
76 OpenIdConnect {
78 id_token: String,
79 access_token: Option<String>,
80 refresh_token: Option<String>,
81 },
82
83 DeviceCode {
85 device_code: String,
86 client_id: String,
87 },
88}
89
90impl Credential {
91 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 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 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 pub fn oauth_refresh(refresh_token: impl Into<String>) -> Self {
124 Self::OAuthRefresh {
125 refresh_token: refresh_token.into(),
126 }
127 }
128
129 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 pub fn api_key(key: impl Into<String>) -> Self {
139 Self::ApiKey {
140 key: key.into(),
141 }
142 }
143
144 pub fn jwt(token: impl Into<String>) -> Self {
146 Self::Jwt {
147 token: token.into(),
148 }
149 }
150
151 pub fn bearer(token: impl Into<String>) -> Self {
153 Self::Bearer {
154 token: token.into(),
155 }
156 }
157
158 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 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 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 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 pub fn saml(assertion: impl Into<String>) -> Self {
204 Self::Saml {
205 assertion: assertion.into(),
206 relay_state: None,
207 }
208 }
209
210 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 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 pub fn supports_refresh(&self) -> bool {
240 matches!(
241 self,
242 Self::OAuth { .. } | Self::OAuthRefresh { .. } | Self::OpenIdConnect { .. }
243 )
244 }
245
246 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 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 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
286pub struct CredentialMetadata {
287 pub client_id: Option<String>,
289
290 pub scopes: Vec<String>,
292
293 pub user_agent: Option<String>,
295
296 pub client_ip: Option<String>,
298
299 pub custom: HashMap<String, String>,
301}
302
303impl CredentialMetadata {
304 pub fn new() -> Self {
306 Self::default()
307 }
308
309 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 pub fn scope(mut self, scope: impl Into<String>) -> Self {
317 self.scopes.push(scope.into());
318 self
319 }
320
321 pub fn scopes(mut self, scopes: Vec<String>) -> Self {
323 self.scopes = scopes;
324 self
325 }
326
327 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct AuthRequest {
349 pub credential: Credential,
351
352 pub metadata: CredentialMetadata,
354
355 pub timestamp: chrono::DateTime<chrono::Utc>,
357}
358
359impl AuthRequest {
360 pub fn new(credential: Credential) -> Self {
362 Self {
363 credential,
364 metadata: CredentialMetadata::default(),
365 timestamp: chrono::Utc::now(),
366 }
367 }
368
369 pub fn with_metadata(credential: Credential, metadata: CredentialMetadata) -> Self {
371 Self {
372 credential,
373 metadata,
374 timestamp: chrono::Utc::now(),
375 }
376 }
377
378 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}