koprogo_api/application/use_cases/
auth_use_cases.rs

1use 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        // Find user by email
33        let user = self
34            .user_repo
35            .find_by_email(&request.email)
36            .await?
37            .ok_or("Invalid email or password")?;
38
39        // Check if user is active
40        if !user.is_active {
41            return Err("User account is deactivated".to_string());
42        }
43
44        // Verify password
45        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        // Generate JWT access token (15 minutes)
53        let token = self.generate_token(&user)?;
54
55        // Generate refresh token (7 days)
56        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        // Check if email already exists
77        if (self.user_repo.find_by_email(&request.email).await?).is_some() {
78            return Err("Email already exists".to_string());
79        }
80
81        // Parse role
82        let role: UserRole = request
83            .role
84            .parse()
85            .map_err(|e| format!("Invalid role: {}", e))?;
86
87        // Hash password
88        let password_hash = hash(&request.password, DEFAULT_COST)
89            .map_err(|e| format!("Failed to hash password: {}", e))?;
90
91        // Create user
92        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        // Save user
102        let created_user = self.user_repo.create(&user).await?;
103
104        // Generate JWT access token (15 minutes)
105        let token = self.generate_token(&created_user)?;
106
107        // Generate refresh token (7 days)
108        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    /// Refresh access token using a refresh token
159    pub async fn refresh_token(
160        &self,
161        request: RefreshTokenRequest,
162    ) -> Result<LoginResponse, String> {
163        // Find refresh token in database
164        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        // Check if token is valid
171        if !refresh_token.is_valid() {
172            return Err("Refresh token expired or revoked".to_string());
173        }
174
175        // Get user
176        let user = self
177            .user_repo
178            .find_by_id(refresh_token.user_id)
179            .await?
180            .ok_or("User not found")?;
181
182        // Check if user is active
183        if !user.is_active {
184            return Err("User account is deactivated".to_string());
185        }
186
187        // Generate new access token (15 minutes)
188        let token = self.generate_token(&user)?;
189
190        // Generate new refresh token (7 days) and revoke old one
191        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    /// Revoke all refresh tokens for a user (logout from all devices)
215    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); // 15 minutes
222
223        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}