koprogo_api/application/use_cases/
auth_use_cases.rs1use crate::application::dto::{
2 Claims, LoginRequest, LoginResponse, RefreshTokenRequest, RegisterRequest, UserResponse,
3};
4use crate::application::ports::{RefreshTokenRepository, UserRepository};
5use crate::domain::entities::{RefreshToken, User, UserRole};
6use bcrypt::{hash, verify, DEFAULT_COST};
7use chrono::Utc;
8use jsonwebtoken::{encode, EncodingKey, Header};
9use std::sync::Arc;
10use uuid::Uuid;
11
12pub struct AuthUseCases {
13 user_repo: Arc<dyn UserRepository>,
14 refresh_token_repo: Arc<dyn RefreshTokenRepository>,
15 jwt_secret: String,
16}
17
18impl AuthUseCases {
19 pub fn new(
20 user_repo: Arc<dyn UserRepository>,
21 refresh_token_repo: Arc<dyn RefreshTokenRepository>,
22 jwt_secret: String,
23 ) -> Self {
24 Self {
25 user_repo,
26 refresh_token_repo,
27 jwt_secret,
28 }
29 }
30
31 pub async fn login(&self, request: LoginRequest) -> Result<LoginResponse, String> {
32 let user = self
34 .user_repo
35 .find_by_email(&request.email)
36 .await?
37 .ok_or("Invalid email or password")?;
38
39 if !user.is_active {
41 return Err("User account is deactivated".to_string());
42 }
43
44 let is_valid = verify(&request.password, &user.password_hash)
46 .map_err(|e| format!("Password verification failed: {}", e))?;
47
48 if !is_valid {
49 return Err("Invalid email or password".to_string());
50 }
51
52 let token = self.generate_token(&user)?;
54
55 let refresh_token_string = self.generate_refresh_token_string(&user);
57 let refresh_token = RefreshToken::new(user.id, refresh_token_string.clone());
58 self.refresh_token_repo.create(&refresh_token).await?;
59
60 Ok(LoginResponse {
61 token,
62 refresh_token: refresh_token_string,
63 user: UserResponse {
64 id: user.id,
65 email: user.email,
66 first_name: user.first_name,
67 last_name: user.last_name,
68 role: user.role.to_string(),
69 organization_id: user.organization_id,
70 is_active: user.is_active,
71 },
72 })
73 }
74
75 pub async fn register(&self, request: RegisterRequest) -> Result<LoginResponse, String> {
76 if (self.user_repo.find_by_email(&request.email).await?).is_some() {
78 return Err("Email already exists".to_string());
79 }
80
81 let role: UserRole = request
83 .role
84 .parse()
85 .map_err(|e| format!("Invalid role: {}", e))?;
86
87 let password_hash = hash(&request.password, DEFAULT_COST)
89 .map_err(|e| format!("Failed to hash password: {}", e))?;
90
91 let user = User::new(
93 request.email,
94 password_hash,
95 request.first_name,
96 request.last_name,
97 role,
98 request.organization_id,
99 )?;
100
101 let created_user = self.user_repo.create(&user).await?;
103
104 let token = self.generate_token(&created_user)?;
106
107 let refresh_token_string = self.generate_refresh_token_string(&created_user);
109 let refresh_token = RefreshToken::new(created_user.id, refresh_token_string.clone());
110 self.refresh_token_repo.create(&refresh_token).await?;
111
112 Ok(LoginResponse {
113 token,
114 refresh_token: refresh_token_string,
115 user: UserResponse {
116 id: created_user.id,
117 email: created_user.email,
118 first_name: created_user.first_name,
119 last_name: created_user.last_name,
120 role: created_user.role.to_string(),
121 organization_id: created_user.organization_id,
122 is_active: created_user.is_active,
123 },
124 })
125 }
126
127 pub async fn get_user_by_id(&self, user_id: uuid::Uuid) -> Result<UserResponse, String> {
128 let user = self
129 .user_repo
130 .find_by_id(user_id)
131 .await?
132 .ok_or("User not found")?;
133
134 Ok(UserResponse {
135 id: user.id,
136 email: user.email,
137 first_name: user.first_name,
138 last_name: user.last_name,
139 role: user.role.to_string(),
140 organization_id: user.organization_id,
141 is_active: user.is_active,
142 })
143 }
144
145 pub fn verify_token(&self, token: &str) -> Result<Claims, String> {
146 use jsonwebtoken::{decode, DecodingKey, Validation};
147
148 let token_data = decode::<Claims>(
149 token,
150 &DecodingKey::from_secret(self.jwt_secret.as_bytes()),
151 &Validation::default(),
152 )
153 .map_err(|e| format!("Invalid token: {}", e))?;
154
155 Ok(token_data.claims)
156 }
157
158 pub async fn refresh_token(
160 &self,
161 request: RefreshTokenRequest,
162 ) -> Result<LoginResponse, String> {
163 let refresh_token = self
165 .refresh_token_repo
166 .find_by_token(&request.refresh_token)
167 .await?
168 .ok_or("Invalid refresh token")?;
169
170 if !refresh_token.is_valid() {
172 return Err("Refresh token expired or revoked".to_string());
173 }
174
175 let user = self
177 .user_repo
178 .find_by_id(refresh_token.user_id)
179 .await?
180 .ok_or("User not found")?;
181
182 if !user.is_active {
184 return Err("User account is deactivated".to_string());
185 }
186
187 let token = self.generate_token(&user)?;
189
190 self.refresh_token_repo
192 .revoke(&request.refresh_token)
193 .await?;
194
195 let new_refresh_token_string = self.generate_refresh_token_string(&user);
196 let new_refresh_token = RefreshToken::new(user.id, new_refresh_token_string.clone());
197 self.refresh_token_repo.create(&new_refresh_token).await?;
198
199 Ok(LoginResponse {
200 token,
201 refresh_token: new_refresh_token_string,
202 user: UserResponse {
203 id: user.id,
204 email: user.email.clone(),
205 first_name: user.first_name.clone(),
206 last_name: user.last_name.clone(),
207 role: user.role.to_string(),
208 organization_id: user.organization_id,
209 is_active: user.is_active,
210 },
211 })
212 }
213
214 pub async fn revoke_all_refresh_tokens(&self, user_id: Uuid) -> Result<u64, String> {
216 self.refresh_token_repo.revoke_all_for_user(user_id).await
217 }
218
219 fn generate_token(&self, user: &User) -> Result<String, String> {
220 let now = Utc::now().timestamp();
221 let expiration = now + (15 * 60); let claims = Claims {
224 sub: user.id.to_string(),
225 email: user.email.clone(),
226 role: user.role.to_string(),
227 organization_id: user.organization_id,
228 exp: expiration,
229 iat: now,
230 };
231
232 encode(
233 &Header::default(),
234 &claims,
235 &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
236 )
237 .map_err(|e| format!("Failed to generate token: {}", e))
238 }
239
240 fn generate_refresh_token_string(&self, user: &User) -> String {
241 let now = Utc::now().timestamp();
242 let random_suffix = Uuid::new_v4();
243 format!("{}:{}:{}", user.id, now, random_suffix)
244 }
245}