1use anyhow::Result;
25use async_trait::async_trait;
26use serde::{Serialize, Deserialize};
27use uuid::Uuid;
28use chrono::{DateTime, Utc};
29
30pub trait AuthenticatableUser: Clone + Send + Sync {
38 fn id(&self) -> &Uuid;
40
41 fn email(&self) -> &str;
43
44 fn password_hash(&self) -> &str;
46
47 fn is_active(&self) -> bool;
49
50 fn is_locked(&self) -> bool;
52
53 fn roles(&self) -> &[String];
55
56 fn two_factor_enabled(&self) -> bool { false }
58
59 fn two_factor_methods(&self) -> &[String] { &[] }
61
62 fn account_expires_at(&self) -> Option<DateTime<Utc>> { None }
64
65 fn requires_password_change(&self) -> bool { false }
67
68 fn last_login_at(&self) -> Option<DateTime<Utc>> { None }
70
71 fn failed_login_attempts(&self) -> u32 { 0 }
73
74 fn locked_until(&self) -> Option<DateTime<Utc>> { None }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SimpleUser {
88 pub id: Uuid,
89 pub email: String,
90 pub password_hash: String,
91 pub is_active: bool,
92 pub is_locked: bool,
93 pub roles: Vec<String>,
94 pub two_factor_enabled: bool,
95 pub two_factor_methods: Vec<String>,
96 pub account_expires_at: Option<DateTime<Utc>>,
97 pub requires_password_change: bool,
98 pub last_login_at: Option<DateTime<Utc>>,
99 pub failed_login_attempts: u32,
100 pub locked_until: Option<DateTime<Utc>>,
101 pub created_at: DateTime<Utc>,
102 pub updated_at: DateTime<Utc>,
103}
104
105impl AuthenticatableUser for SimpleUser {
106 fn id(&self) -> &Uuid { &self.id }
107 fn email(&self) -> &str { &self.email }
108 fn password_hash(&self) -> &str { &self.password_hash }
109 fn is_active(&self) -> bool { self.is_active }
110 fn is_locked(&self) -> bool { self.is_locked }
111 fn roles(&self) -> &[String] { &self.roles }
112 fn two_factor_enabled(&self) -> bool { self.two_factor_enabled }
113 fn two_factor_methods(&self) -> &[String] { &self.two_factor_methods }
114 fn account_expires_at(&self) -> Option<DateTime<Utc>> { self.account_expires_at }
115 fn requires_password_change(&self) -> bool { self.requires_password_change }
116 fn last_login_at(&self) -> Option<DateTime<Utc>> { self.last_login_at }
117 fn failed_login_attempts(&self) -> u32 { self.failed_login_attempts }
118 fn locked_until(&self) -> Option<DateTime<Utc>> { self.locked_until }
119}
120
121pub type User = SimpleUser;
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct RefreshTokenClaims {
133 pub sub: String,
134 pub exp: usize,
135 pub iat: usize,
136 pub iss: String,
137 pub token_type: String,
138}
139
140#[async_trait]
144pub trait UserRepository<U: AuthenticatableUser = SimpleUser>: Send + Sync {
145 async fn find_by_email(&self, email: &str) -> Result<Option<U>>;
147
148 async fn find_by_id(&self, id: &Uuid) -> Result<Option<U>>;
150
151 async fn create(&self, user: &U) -> Result<U>;
153
154 async fn update(&self, user: &U) -> Result<U>;
156
157 async fn update_last_login(&self, user_id: &Uuid) -> Result<()>;
159
160 async fn increment_failed_attempts(&self, user_id: &Uuid) -> Result<()>;
162
163 async fn reset_failed_attempts(&self, user_id: &Uuid) -> Result<()>;
165
166 async fn lock_account(&self, user_id: &Uuid, locked_until: Option<DateTime<Utc>>) -> Result<()>;
168
169 async fn unlock_account(&self, user_id: &Uuid) -> Result<()>;
171
172 async fn exists_by_email(&self, email: &str) -> Result<bool>;
174}
175
176#[async_trait]
178pub trait SecurityService: Send + Sync {
179 async fn check_rate_limit(&self, email: &str, ip_address: Option<&str>) -> Result<()>;
181
182 async fn log_failed_auth_attempt(&self, user_id: &Uuid, ip_address: Option<&str>) -> Result<()>;
184
185 async fn log_successful_auth(&self, user_id: &Uuid, ip_address: Option<&str>) -> Result<()>;
187
188 async fn analyze_login_attempt(
190 &self,
191 user_id: &Uuid,
192 device_info: &Option<DeviceInfo>,
193 ip_address: Option<&str>
194 ) -> Result<SecurityFlags>;
195
196 async fn generate_password_reset_token(&self, user_id: &Uuid) -> Result<String>;
198
199 async fn validate_password_reset_token(&self, token: &str) -> Result<Option<Uuid>>;
201
202 async fn send_security_alert(&self, user_id: &Uuid, alert_type: SecurityAlertType, details: &str) -> Result<()>;
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct DeviceInfo {
209 pub device_id: Option<String>,
210 pub device_type: String, pub platform: Option<String>, pub user_agent: Option<String>,
213 pub fingerprint: Option<String>,
214}
215
216#[derive(Debug, Clone, Default, Serialize, Deserialize)]
218pub struct SecurityFlags {
219 pub new_device: bool,
220 pub new_location: bool,
221 pub suspicious_activity: bool,
222 pub requires_password_change: bool,
223 pub risk_score: f32, }
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub enum SecurityAlertType {
229 NewDeviceLogin,
230 NewLocationLogin,
231 SuspiciousActivity,
232 AccountLocked,
233 PasswordReset,
234 MultipleFailedAttempts,
235}
236
237#[derive(Debug, Clone)]
239pub struct AuthContext {
240 pub ip_address: Option<String>,
241 pub user_agent: Option<String>,
242 pub device_fingerprint: Option<String>,
243 pub timestamp: DateTime<Utc>,
244 pub session_id: Option<String>,
245}
246
247impl Default for AuthContext {
248 fn default() -> Self {
249 Self {
250 ip_address: None,
251 user_agent: None,
252 device_fingerprint: None,
253 timestamp: Utc::now(),
254 session_id: None,
255 }
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct PasswordPolicy {
262 pub min_length: usize,
263 pub max_length: usize,
264 pub require_uppercase: bool,
265 pub require_lowercase: bool,
266 pub require_numbers: bool,
267 pub require_special_chars: bool,
268 pub forbidden_patterns: Vec<String>,
269 pub common_passwords: Vec<String>,
270}
271
272impl Default for PasswordPolicy {
273 fn default() -> Self {
274 Self {
275 min_length: 8,
276 max_length: 128,
277 require_uppercase: true,
278 require_lowercase: true,
279 require_numbers: true,
280 require_special_chars: false,
281 forbidden_patterns: vec![
282 "password".to_string(),
283 "123456".to_string(),
284 "qwerty".to_string(),
285 ],
286 common_passwords: vec![
287 "password".to_string(),
288 "123456".to_string(),
289 "123456789".to_string(),
290 "qwerty".to_string(),
291 "abc123".to_string(),
292 "password123".to_string(),
293 "admin".to_string(),
294 "letmein".to_string(),
295 "welcome".to_string(),
296 "monkey".to_string(),
297 ],
298 }
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304pub enum TwoFactorMethod {
305 TOTP, SMS, Email, BackupCode, }
310
311#[derive(Debug, Clone)]
313pub struct TwoFactorChallenge {
314 pub user_id: Uuid,
315 pub method: TwoFactorMethod,
316 pub challenge: String,
317 pub expires_at: DateTime<Utc>,
318}
319
320#[derive(Debug, Clone)]
322pub struct PasswordResetRequest {
323 pub email: String,
324 pub context: AuthContext,
325}
326
327#[derive(Debug, Clone)]
329pub struct PasswordResetConfirmation {
330 pub token: String,
331 pub new_password: String,
332 pub context: AuthContext,
333}
334
335#[derive(Debug, Clone)]
337pub struct AuthRequest {
338 pub email: String,
339 pub password: String,
340 pub remember_me: Option<bool>,
341 pub device_info: Option<DeviceInfo>,
342 pub ip_address: Option<String>,
343 pub user_agent: Option<String>,
344}
345
346#[derive(Debug, Clone)]
348pub struct AuthResultEnhanced {
349 pub user_id: Uuid,
350 pub token: String,
351 pub refresh_token: Option<String>,
352 pub expires_at: DateTime<Utc>,
353 pub requires_2fa: bool,
354 pub security_flags: SecurityFlags,
355}