1use crate::application::dto::{
2 Claims, LoginRequest, LoginResponse, RefreshTokenRequest, RegisterRequest, UserResponse,
3 UserRoleSummary,
4};
5use crate::application::ports::{RefreshTokenRepository, UserRepository, UserRoleRepository};
6use crate::domain::entities::{RefreshToken, User, UserRole, UserRoleAssignment};
7use crate::infrastructure::audit::{log_audit_event, AuditEventType};
8use bcrypt::{hash, verify, DEFAULT_COST};
9use chrono::Utc;
10use jsonwebtoken::{encode, EncodingKey, Header};
11use std::sync::Arc;
12use uuid::Uuid;
13
14pub struct AuthUseCases {
15 user_repo: Arc<dyn UserRepository>,
16 refresh_token_repo: Arc<dyn RefreshTokenRepository>,
17 user_role_repo: Arc<dyn UserRoleRepository>,
18 jwt_secret: String,
19}
20
21impl AuthUseCases {
22 pub fn new(
23 user_repo: Arc<dyn UserRepository>,
24 refresh_token_repo: Arc<dyn RefreshTokenRepository>,
25 user_role_repo: Arc<dyn UserRoleRepository>,
26 jwt_secret: String,
27 ) -> Self {
28 Self {
29 user_repo,
30 refresh_token_repo,
31 user_role_repo,
32 jwt_secret,
33 }
34 }
35
36 pub async fn login(&self, request: LoginRequest) -> Result<LoginResponse, String> {
37 let user = self
38 .user_repo
39 .find_by_email(&request.email)
40 .await?
41 .ok_or_else(|| {
42 tokio::spawn(async move {
44 log_audit_event(
45 AuditEventType::AuthenticationFailed,
46 None,
47 None,
48 Some(format!("Failed login attempt for email: {}", request.email)),
49 None,
50 )
51 .await;
52 });
53 "Invalid email or password".to_string()
54 })?;
55
56 if !user.is_active {
57 let user_id = user.id;
59 tokio::spawn(async move {
60 log_audit_event(
61 AuditEventType::AuthenticationFailed,
62 Some(user_id),
63 None,
64 Some("Login attempt on deactivated account".to_string()),
65 None,
66 )
67 .await;
68 });
69 return Err("User account is deactivated".to_string());
70 }
71
72 let is_valid = verify(&request.password, &user.password_hash)
73 .map_err(|e| format!("Password verification failed: {}", e))?;
74
75 if !is_valid {
76 let user_id = user.id;
78 tokio::spawn(async move {
79 log_audit_event(
80 AuditEventType::AuthenticationFailed,
81 Some(user_id),
82 None,
83 Some("Invalid password".to_string()),
84 None,
85 )
86 .await;
87 });
88 return Err("Invalid email or password".to_string());
89 }
90
91 let (roles, active_role) = self.ensure_role_assignments(&user).await?;
92 let user_for_token = self.apply_active_role_metadata(&user, &active_role).await?;
93 let token = self.generate_token(&user_for_token, &active_role)?;
94
95 let refresh_token_string = self.generate_refresh_token_string(&user_for_token);
96 let refresh_token = RefreshToken::new(user_for_token.id, refresh_token_string.clone());
97 self.refresh_token_repo.create(&refresh_token).await?;
98
99 let user_id = user_for_token.id;
101 let organization_id = active_role.organization_id;
102 tokio::spawn(async move {
103 log_audit_event(
104 AuditEventType::UserLogin,
105 Some(user_id),
106 organization_id,
107 Some("Successful login".to_string()),
108 None,
109 )
110 .await;
111 });
112
113 Ok(LoginResponse {
114 token,
115 refresh_token: refresh_token_string,
116 user: self.build_user_response(&user_for_token, &roles, &active_role),
117 })
118 }
119
120 pub async fn register(&self, request: RegisterRequest) -> Result<LoginResponse, String> {
121 if (self.user_repo.find_by_email(&request.email).await?).is_some() {
122 return Err("Email already exists".to_string());
123 }
124
125 let role: UserRole = request
126 .role
127 .parse()
128 .map_err(|e| format!("Invalid role: {}", e))?;
129
130 let password_hash = hash(&request.password, DEFAULT_COST)
131 .map_err(|e| format!("Failed to hash password: {}", e))?;
132
133 let user = User::new(
134 request.email,
135 password_hash,
136 request.first_name,
137 request.last_name,
138 role.clone(),
139 request.organization_id,
140 )?;
141
142 let created_user = self.user_repo.create(&user).await?;
143
144 let primary_assignment = self
146 .user_role_repo
147 .create(&UserRoleAssignment::new(
148 created_user.id,
149 role,
150 created_user.organization_id,
151 true,
152 ))
153 .await?;
154 let roles = vec![primary_assignment.clone()];
155 let user_for_token = self
156 .apply_active_role_metadata(&created_user, &primary_assignment)
157 .await?;
158
159 let token = self.generate_token(&user_for_token, &primary_assignment)?;
160 let refresh_token_string = self.generate_refresh_token_string(&user_for_token);
161 let refresh_token = RefreshToken::new(user_for_token.id, refresh_token_string.clone());
162 self.refresh_token_repo.create(&refresh_token).await?;
163
164 let user_id = created_user.id;
166 let organization_id = created_user.organization_id;
167 let email = created_user.email.clone();
168 tokio::spawn(async move {
169 log_audit_event(
170 AuditEventType::UserRegistration,
171 Some(user_id),
172 organization_id,
173 Some(format!("New user registered: {}", email)),
174 None,
175 )
176 .await;
177 });
178
179 Ok(LoginResponse {
180 token,
181 refresh_token: refresh_token_string,
182 user: self.build_user_response(&user_for_token, &roles, &primary_assignment),
183 })
184 }
185
186 pub async fn switch_active_role(
187 &self,
188 user_id: Uuid,
189 role_id: Uuid,
190 ) -> Result<LoginResponse, String> {
191 let user = self
192 .user_repo
193 .find_by_id(user_id)
194 .await?
195 .ok_or("User not found")?;
196
197 if !user.is_active {
198 return Err("User account is deactivated".to_string());
199 }
200
201 let target_role = self
202 .user_role_repo
203 .find_by_id(role_id)
204 .await?
205 .ok_or("Role assignment not found")?;
206
207 if target_role.user_id != user.id {
208 return Err("Role assignment does not belong to user".to_string());
209 }
210
211 let updated_primary = self
212 .user_role_repo
213 .set_primary_role(user.id, role_id)
214 .await?;
215
216 let roles = self.user_role_repo.list_for_user(user.id).await?;
217 let active_role = roles
218 .iter()
219 .find(|assignment| assignment.is_primary)
220 .cloned()
221 .unwrap_or(updated_primary.clone());
222
223 let updated_user = self.apply_active_role_metadata(&user, &active_role).await?;
224
225 let token = self.generate_token(&updated_user, &active_role)?;
226 let refresh_token_string = self.generate_refresh_token_string(&updated_user);
227 let refresh_token = RefreshToken::new(updated_user.id, refresh_token_string.clone());
228 self.refresh_token_repo.create(&refresh_token).await?;
229
230 Ok(LoginResponse {
231 token,
232 refresh_token: refresh_token_string,
233 user: self.build_user_response(&updated_user, &roles, &active_role),
234 })
235 }
236
237 pub async fn get_user_by_id(&self, user_id: uuid::Uuid) -> Result<UserResponse, String> {
238 let user = self
239 .user_repo
240 .find_by_id(user_id)
241 .await?
242 .ok_or("User not found")?;
243
244 let (roles, active_role) = self.ensure_role_assignments(&user).await?;
245 Ok(self.build_user_response(&user, &roles, &active_role))
246 }
247
248 pub fn verify_token(&self, token: &str) -> Result<Claims, String> {
249 use jsonwebtoken::{decode, DecodingKey, Validation};
250
251 let token_data = decode::<Claims>(
252 token,
253 &DecodingKey::from_secret(self.jwt_secret.as_bytes()),
254 &Validation::default(),
255 )
256 .map_err(|e| format!("Invalid token: {}", e))?;
257
258 Ok(token_data.claims)
259 }
260
261 pub async fn refresh_token(
262 &self,
263 request: RefreshTokenRequest,
264 ) -> Result<LoginResponse, String> {
265 let refresh_token = self
266 .refresh_token_repo
267 .find_by_token(&request.refresh_token)
268 .await?
269 .ok_or_else(|| {
270 tokio::spawn(async {
272 log_audit_event(
273 AuditEventType::InvalidToken,
274 None,
275 None,
276 Some("Invalid refresh token attempted".to_string()),
277 None,
278 )
279 .await;
280 });
281 "Invalid refresh token".to_string()
282 })?;
283
284 if !refresh_token.is_valid() {
285 let user_id = refresh_token.user_id;
287 let reason = if refresh_token.is_expired() {
288 "Expired refresh token"
289 } else {
290 "Revoked refresh token"
291 };
292 tokio::spawn(async move {
293 log_audit_event(
294 AuditEventType::InvalidToken,
295 Some(user_id),
296 None,
297 Some(format!("{} attempted", reason)),
298 None,
299 )
300 .await;
301 });
302 return Err("Refresh token expired or revoked".to_string());
303 }
304
305 let user = self
306 .user_repo
307 .find_by_id(refresh_token.user_id)
308 .await?
309 .ok_or("User not found")?;
310
311 if !user.is_active {
312 let user_id = user.id;
314 tokio::spawn(async move {
315 log_audit_event(
316 AuditEventType::AuthenticationFailed,
317 Some(user_id),
318 None,
319 Some("Refresh token attempt on deactivated account".to_string()),
320 None,
321 )
322 .await;
323 });
324 return Err("User account is deactivated".to_string());
325 }
326
327 let (roles, active_role) = self.ensure_role_assignments(&user).await?;
328 let user_for_token = self.apply_active_role_metadata(&user, &active_role).await?;
329 let token = self.generate_token(&user_for_token, &active_role)?;
330
331 self.refresh_token_repo
333 .revoke(&request.refresh_token)
334 .await?;
335
336 let new_refresh_token_string = self.generate_refresh_token_string(&user_for_token);
337 let new_refresh_token =
338 RefreshToken::new(user_for_token.id, new_refresh_token_string.clone());
339 self.refresh_token_repo.create(&new_refresh_token).await?;
340
341 let user_id = user_for_token.id;
343 let organization_id = active_role.organization_id;
344 tokio::spawn(async move {
345 log_audit_event(
346 AuditEventType::TokenRefresh,
347 Some(user_id),
348 organization_id,
349 Some("Refresh token successfully exchanged".to_string()),
350 None,
351 )
352 .await;
353 });
354
355 Ok(LoginResponse {
356 token,
357 refresh_token: new_refresh_token_string,
358 user: self.build_user_response(&user_for_token, &roles, &active_role),
359 })
360 }
361
362 pub async fn revoke_all_refresh_tokens(&self, user_id: Uuid) -> Result<u64, String> {
363 self.refresh_token_repo.revoke_all_for_user(user_id).await
364 }
365
366 fn build_user_response(
367 &self,
368 user: &User,
369 roles: &[UserRoleAssignment],
370 active_role: &UserRoleAssignment,
371 ) -> UserResponse {
372 UserResponse {
373 id: user.id,
374 email: user.email.clone(),
375 first_name: user.first_name.clone(),
376 last_name: user.last_name.clone(),
377 role: active_role.role.to_string(),
378 organization_id: active_role.organization_id,
379 is_active: user.is_active,
380 roles: roles.iter().map(Self::summarize_role).collect(),
381 active_role: Some(Self::summarize_role(active_role)),
382 }
383 }
384
385 async fn ensure_role_assignments(
386 &self,
387 user: &User,
388 ) -> Result<(Vec<UserRoleAssignment>, UserRoleAssignment), String> {
389 let mut assignments = self.user_role_repo.list_for_user(user.id).await?;
390
391 if assignments.is_empty() {
392 let assignment = self
393 .user_role_repo
394 .create(&UserRoleAssignment::new(
395 user.id,
396 user.role.clone(),
397 user.organization_id,
398 true,
399 ))
400 .await?;
401 assignments.push(assignment.clone());
402 }
403
404 if !assignments.iter().any(|assignment| assignment.is_primary) {
405 let first = assignments[0].id;
406 self.user_role_repo.set_primary_role(user.id, first).await?;
407 assignments = self.user_role_repo.list_for_user(user.id).await?;
408 }
409
410 let active = assignments
411 .iter()
412 .find(|assignment| assignment.is_primary)
413 .cloned()
414 .unwrap_or_else(|| assignments[0].clone());
415
416 Ok((assignments, active))
417 }
418
419 async fn apply_active_role_metadata(
420 &self,
421 user: &User,
422 active_role: &UserRoleAssignment,
423 ) -> Result<User, String> {
424 let mut updated_user = user.clone();
425 let mut requires_update = false;
426
427 if updated_user.role != active_role.role {
428 updated_user.role = active_role.role.clone();
429 requires_update = true;
430 }
431
432 if updated_user.organization_id != active_role.organization_id {
433 updated_user.organization_id = active_role.organization_id;
434 requires_update = true;
435 }
436
437 if requires_update {
438 updated_user.updated_at = Utc::now();
439 return self.user_repo.update(&updated_user).await;
440 }
441
442 Ok(updated_user)
443 }
444
445 fn summarize_role(assignment: &UserRoleAssignment) -> UserRoleSummary {
446 UserRoleSummary {
447 id: assignment.id,
448 role: assignment.role.to_string(),
449 organization_id: assignment.organization_id,
450 is_primary: assignment.is_primary,
451 }
452 }
453
454 fn generate_token(
455 &self,
456 user: &User,
457 active_role: &UserRoleAssignment,
458 ) -> Result<String, String> {
459 let now = Utc::now().timestamp();
460 let expiration = now + (15 * 60);
461
462 let claims = Claims {
463 sub: user.id.to_string(),
464 email: user.email.clone(),
465 role: active_role.role.to_string(),
466 organization_id: active_role.organization_id,
467 role_id: Some(active_role.id),
468 exp: expiration,
469 iat: now,
470 };
471
472 encode(
473 &Header::default(),
474 &claims,
475 &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
476 )
477 .map_err(|e| format!("Failed to generate token: {}", e))
478 }
479
480 fn generate_refresh_token_string(&self, user: &User) -> String {
481 let now = Utc::now().timestamp();
482 format!("{}:{}:{}", user.id, now, uuid::Uuid::new_v4())
483 }
484}