1use crate::{
4 authentication::credentials::{Credential, CredentialMetadata},
5 errors::{AuthError, Result},
6 tokens::AuthToken,
7};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11pub mod enhanced_device;
13pub mod hardware_token;
14pub mod passkey;
15#[cfg(feature = "saml")]
16pub mod saml;
17
18#[cfg(feature = "enhanced-device-flow")]
20pub use enhanced_device::EnhancedDeviceFlowMethod;
21pub use hardware_token::HardwareToken;
22#[cfg(feature = "passkeys")]
23pub use passkey::PasskeyAuthMethod;
24#[cfg(feature = "saml")]
25pub use saml::SamlAuthMethod;
26
27#[derive(Debug, Clone)]
29pub enum MethodResult {
30 Success(Box<AuthToken>),
32
33 MfaRequired(Box<MfaChallenge>),
35
36 Failure { reason: String },
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct MfaChallenge {
43 pub id: String,
45
46 pub mfa_type: MfaType,
48
49 pub user_id: String,
51
52 pub expires_at: chrono::DateTime<chrono::Utc>,
54
55 pub message: Option<String>,
57
58 pub data: HashMap<String, serde_json::Value>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum MfaType {
65 Totp,
67
68 Sms { phone_number: String },
70
71 Email { email_address: String },
73
74 Push { device_id: String },
76
77 SecurityKey,
79
80 BackupCode,
82}
83
84pub trait AuthMethod: Send + Sync {
86 type MethodResult: Send + Sync + 'static;
87 type AuthToken: Send + Sync + 'static;
88
89 fn name(&self) -> &str;
91
92 fn authenticate(
94 &self,
95 credential: Credential,
96 metadata: CredentialMetadata,
97 ) -> impl std::future::Future<Output = Result<Self::MethodResult>> + Send;
98
99 fn validate_config(&self) -> Result<()>;
101
102 fn supports_refresh(&self) -> bool {
104 false
105 }
106
107 fn refresh_token(
109 &self,
110 _refresh_token: String,
111 ) -> impl std::future::Future<Output = Result<AuthToken, AuthError>> + Send {
112 async {
113 Err(AuthError::auth_method(
114 self.name(),
115 "Token refresh not supported by this method".to_string(),
116 ))
117 }
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct UserInfo {
124 pub id: String,
126
127 pub username: String,
129
130 pub email: Option<String>,
132
133 pub name: Option<String>,
135
136 pub roles: Vec<String>,
138
139 pub active: bool,
141
142 pub attributes: HashMap<String, serde_json::Value>,
144}
145
146pub enum AuthMethodEnum {
148 Password(PasswordMethod),
149 Jwt(JwtMethod),
150 ApiKey(ApiKeyMethod),
151 OAuth2(OAuth2Method),
152 #[cfg(feature = "saml")]
153 Saml(SamlAuthMethod),
154 #[cfg(feature = "ldap-auth")]
155 Ldap(LdapAuthMethod),
156 HardwareToken(HardwareToken),
157 OpenIdConnect(OpenIdConnectAuthMethod),
158 AdvancedMfa(AdvancedMfaAuthMethod),
159 #[cfg(feature = "enhanced-device-flow")]
160 EnhancedDeviceFlow(Box<enhanced_device::EnhancedDeviceFlowMethod>),
161 #[cfg(feature = "passkeys")]
162 Passkey(PasskeyAuthMethod),
163}
164
165#[derive(Debug)]
167pub struct PasswordMethod;
168
169#[derive(Debug)]
170pub struct JwtMethod;
171
172#[derive(Debug)]
173pub struct ApiKeyMethod;
174
175#[derive(Debug)]
176pub struct OAuth2Method;
177
178#[cfg(feature = "ldap-auth")]
179#[derive(Debug)]
180pub struct LdapAuthMethod;
181
182#[derive(Debug)]
183pub struct OpenIdConnectAuthMethod;
184
185#[derive(Debug)]
186pub struct AdvancedMfaAuthMethod;
187
188impl Default for PasswordMethod {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl PasswordMethod {
196 pub fn new() -> Self {
197 Self
198 }
199}
200
201impl Default for JwtMethod {
202 fn default() -> Self {
203 Self::new()
204 }
205}
206
207impl JwtMethod {
208 pub fn new() -> Self {
209 Self
210 }
211
212 pub fn secret_key(self, _secret: &str) -> Self {
213 self
214 }
215
216 pub fn issuer(self, _issuer: &str) -> Self {
217 self
218 }
219
220 pub fn audience(self, _audience: &str) -> Self {
221 self
222 }
223}
224
225impl Default for ApiKeyMethod {
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231impl ApiKeyMethod {
232 pub fn new() -> Self {
233 Self
234 }
235}
236
237impl Default for OAuth2Method {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243impl OAuth2Method {
244 pub fn new() -> Self {
245 Self
246 }
247}
248
249impl AuthMethod for AuthMethodEnum {
250 type MethodResult = MethodResult;
251 type AuthToken = AuthToken;
252
253 fn name(&self) -> &str {
254 match self {
255 AuthMethodEnum::Password(_) => "password",
256 AuthMethodEnum::Jwt(_) => "jwt",
257 AuthMethodEnum::ApiKey(_) => "api_key",
258 AuthMethodEnum::OAuth2(_) => "oauth2",
259 #[cfg(feature = "saml")]
260 AuthMethodEnum::Saml(_) => "saml",
261 #[cfg(feature = "ldap-auth")]
262 AuthMethodEnum::Ldap(_) => "ldap",
263 AuthMethodEnum::HardwareToken(_) => "hardware_token",
264 AuthMethodEnum::OpenIdConnect(_) => "openid_connect",
265 AuthMethodEnum::AdvancedMfa(_) => "advanced_mfa",
266 #[cfg(feature = "enhanced-device-flow")]
267 AuthMethodEnum::EnhancedDeviceFlow(_) => "enhanced_device_flow",
268 #[cfg(feature = "passkeys")]
269 AuthMethodEnum::Passkey(_) => "passkey",
270 }
271 }
272
273 async fn authenticate(
274 &self,
275 credential: Credential,
276 metadata: CredentialMetadata,
277 ) -> Result<Self::MethodResult> {
278 match &credential {
282 Credential::Password { username, password } => {
283 if username.is_empty() || password.is_empty() {
284 return Ok(MethodResult::Failure {
285 reason: "Username or password cannot be empty".to_string(),
286 });
287 }
288 }
289 Credential::Jwt { token } => {
290 if token.is_empty() {
291 return Ok(MethodResult::Failure {
292 reason: "JWT token cannot be empty".to_string(),
293 });
294 }
295 }
296 Credential::ApiKey { key } => {
297 if key.is_empty() {
298 return Ok(MethodResult::Failure {
299 reason: "API key cannot be empty".to_string(),
300 });
301 }
302 }
303 _ => {
304 }
306 }
307
308 if let Some(ip) = &metadata.client_ip
310 && ip.starts_with("127.")
311 {
312 tracing::warn!("Authentication attempt from localhost");
313 }
314
315 tracing::warn!(
318 "Using default authentication method - this should not happen in production"
319 );
320
321 Ok(MethodResult::Failure {
323 reason:
324 "Authentication method not fully implemented - please use a concrete implementation"
325 .to_string(),
326 })
327 }
328
329 fn validate_config(&self) -> Result<()> {
330 Ok(())
332 }
333
334 fn supports_refresh(&self) -> bool {
335 false
336 }
337
338 async fn refresh_token(&self, _refresh_token: String) -> Result<AuthToken, AuthError> {
339 Err(AuthError::auth_method(
340 self.name(),
341 "Token refresh not supported by this method".to_string(),
342 ))
343 }
344}
345
346impl MfaChallenge {
347 pub fn new(
349 mfa_type: MfaType,
350 user_id: impl Into<String>,
351 expires_in: std::time::Duration,
352 ) -> Self {
353 Self {
354 id: uuid::Uuid::new_v4().to_string(),
355 mfa_type,
356 user_id: user_id.into(),
357 expires_at: chrono::Utc::now() + chrono::Duration::from_std(expires_in).unwrap(),
358 message: None,
359 data: HashMap::new(),
360 }
361 }
362
363 pub fn id(&self) -> &str {
365 &self.id
366 }
367
368 pub fn is_expired(&self) -> bool {
370 chrono::Utc::now() > self.expires_at
371 }
372
373 pub fn with_message(mut self, message: impl Into<String>) -> Self {
374 self.message = Some(message.into());
375 self
376 }
377}
378
379