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
84impl Credential {
85 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 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 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 pub fn oauth_refresh(refresh_token: impl Into<String>) -> Self {
118 Self::OAuthRefresh {
119 refresh_token: refresh_token.into(),
120 }
121 }
122
123 pub fn api_key(key: impl Into<String>) -> Self {
125 Self::ApiKey {
126 key: key.into(),
127 }
128 }
129
130 pub fn jwt(token: impl Into<String>) -> Self {
132 Self::Jwt {
133 token: token.into(),
134 }
135 }
136
137 pub fn bearer(token: impl Into<String>) -> Self {
139 Self::Bearer {
140 token: token.into(),
141 }
142 }
143
144 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 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 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 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 pub fn saml(assertion: impl Into<String>) -> Self {
190 Self::Saml {
191 assertion: assertion.into(),
192 relay_state: None,
193 }
194 }
195
196 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 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 pub fn supports_refresh(&self) -> bool {
225 matches!(
226 self,
227 Self::OAuth { .. } | Self::OAuthRefresh { .. } | Self::OpenIdConnect { .. }
228 )
229 }
230
231 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 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 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
270pub struct CredentialMetadata {
271 pub client_id: Option<String>,
273
274 pub scopes: Vec<String>,
276
277 pub user_agent: Option<String>,
279
280 pub client_ip: Option<String>,
282
283 pub custom: HashMap<String, String>,
285}
286
287impl CredentialMetadata {
288 pub fn new() -> Self {
290 Self::default()
291 }
292
293 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 pub fn scope(mut self, scope: impl Into<String>) -> Self {
301 self.scopes.push(scope.into());
302 self
303 }
304
305 pub fn scopes(mut self, scopes: Vec<String>) -> Self {
307 self.scopes = scopes;
308 self
309 }
310
311 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct AuthRequest {
333 pub credential: Credential,
335
336 pub metadata: CredentialMetadata,
338
339 pub timestamp: chrono::DateTime<chrono::Utc>,
341}
342
343impl AuthRequest {
344 pub fn new(credential: Credential) -> Self {
346 Self {
347 credential,
348 metadata: CredentialMetadata::default(),
349 timestamp: chrono::Utc::now(),
350 }
351 }
352
353 pub fn with_metadata(credential: Credential, metadata: CredentialMetadata) -> Self {
355 Self {
356 credential,
357 metadata,
358 timestamp: chrono::Utc::now(),
359 }
360 }
361
362 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}