1use crate::error::AuthError;
2use crate::jwt::{JwtClaims, JwtEncoder};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7pub trait PasswordVerifier: Send + Sync {
29 fn verify_password(&self, username: &str, password: &str) -> Result<i64, AuthError>;
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Credentials {
39 pub username: String,
40 pub password: String,
41}
42
43impl Credentials {
44 pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
45 Self {
46 username: username.into(),
47 password: password.into(),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Token {
54 pub access_token: String,
55 pub refresh_token: Option<String>,
56 pub token_type: String,
57 pub expires_in: u64,
58 pub issued_at: i64,
59}
60
61impl Token {
62 pub fn new(access_token: impl Into<String>, expires_in: u64) -> Self {
63 Self {
64 access_token: access_token.into(),
65 refresh_token: None,
66 token_type: "Bearer".to_string(),
67 expires_in,
68 issued_at: current_timestamp(),
69 }
70 }
71
72 pub fn with_refresh(mut self, refresh_token: impl Into<String>) -> Self {
73 self.refresh_token = Some(refresh_token.into());
74 self
75 }
76
77 pub fn is_expired(&self) -> bool {
78 let now = current_timestamp();
79 let expiry = self.issued_at + (self.expires_in as i64 * 1000);
80 now > expiry
81 }
82
83 pub fn expires_at(&self) -> i64 {
84 self.issued_at + (self.expires_in as i64 * 1000)
85 }
86}
87
88fn current_timestamp() -> i64 {
89 use std::time::{SystemTime, UNIX_EPOCH};
90 SystemTime::now()
91 .duration_since(UNIX_EPOCH)
92 .unwrap_or_default()
93 .as_millis() as i64
94}
95
96fn current_timestamp_secs() -> i64 {
97 use std::time::{SystemTime, UNIX_EPOCH};
98 SystemTime::now()
99 .duration_since(UNIX_EPOCH)
100 .unwrap_or_default()
101 .as_secs() as i64
102}
103
104pub struct User {
105 pub id: i64,
106 pub username: String,
107 pub email: Option<String>,
108 pub roles: Vec<String>,
109 pub permissions: Vec<String>,
110 pub metadata: HashMap<String, serde_json::Value>,
111}
112
113impl User {
114 pub fn new(id: i64, username: impl Into<String>) -> Self {
115 Self {
116 id,
117 username: username.into(),
118 email: None,
119 roles: Vec::new(),
120 permissions: Vec::new(),
121 metadata: HashMap::new(),
122 }
123 }
124
125 pub fn with_email(mut self, email: impl Into<String>) -> Self {
126 self.email = Some(email.into());
127 self
128 }
129
130 pub fn with_roles(mut self, roles: Vec<String>) -> Self {
131 self.roles = roles;
132 self
133 }
134
135 pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
136 self.permissions = permissions;
137 self
138 }
139
140 pub fn has_role(&self, role: &str) -> bool {
141 self.roles.iter().any(|r| r == role)
142 }
143
144 pub fn has_permission(&self, permission: &str) -> bool {
145 self.permissions.iter().any(|p| p == permission) || self.has_role("admin")
146 }
147}
148
149pub struct JwtAuthenticator {
151 encoder: JwtEncoder,
152 issuer: String,
153 expiration: u64,
154 password_verifier: Option<Arc<dyn PasswordVerifier>>,
159}
160
161impl JwtAuthenticator {
162 pub fn new(secret: impl Into<String>, issuer: impl Into<String>, expiration: u64) -> Self {
163 Self {
164 encoder: JwtEncoder::new(secret),
165 issuer: issuer.into(),
166 expiration,
167 password_verifier: None,
168 }
169 }
170
171 pub fn with_password_verifier(mut self, verifier: Arc<dyn PasswordVerifier>) -> Self {
176 self.password_verifier = Some(verifier);
177 self
178 }
179
180 pub fn authenticate(&self, credentials: &Credentials) -> Result<Token, AuthError> {
181 if credentials.username.is_empty() || credentials.password.is_empty() {
182 return Err(AuthError::InvalidCredentials(
183 "Username or password is empty".to_string(),
184 ));
185 }
186
187 let user_id: i64 = if let Some(verifier) = &self.password_verifier {
189 verifier.verify_password(&credentials.username, &credentials.password)?
190 } else {
191 eprintln!(
194 "[warn] JwtAuthenticator::authenticate: password_verifier not configured; \
195 accepting credentials without password verification (Critical S-1)"
196 );
197 0
198 };
199
200 let exp = current_timestamp_secs() + (self.expiration as i64);
201 let claims = JwtClaims::new(credentials.username.clone(), exp)
202 .with_issuer(self.issuer.clone())
203 .with_roles(vec!["user".to_string()])
204 .with_user_id(user_id);
205
206 let access_token = self.encoder.encode(&claims)?;
207 let refresh_claims = JwtClaims::new(credentials.username.clone(), exp + 86400)
208 .with_issuer(self.issuer.clone())
209 .with_user_id(user_id);
210 let refresh_token = self.encoder.encode(&refresh_claims)?;
211
212 Ok(Token::new(access_token, self.expiration).with_refresh(refresh_token))
213 }
214
215 pub fn verify_token(&self, token: &str) -> Result<User, AuthError> {
216 if token.is_empty() {
217 return Err(AuthError::TokenInvalid("Token is empty".to_string()));
218 }
219
220 let claims = self.encoder.decode(token)?;
221 let user_id = claims.user_id.unwrap_or_else(|| {
223 eprintln!(
224 "[warn] JwtAuthenticator::verify_token: token has no user_id claim \
225 (legacy token); falling back to user.id=0 (Critical S-2)"
226 );
227 0
228 });
229 let user = User::new(user_id, claims.sub.clone())
230 .with_roles(claims.roles.clone())
231 .with_permissions(claims.permissions.clone());
232 Ok(user)
233 }
234
235 pub fn refresh_token(&self, refresh_token: &str) -> Result<Token, AuthError> {
236 if refresh_token.is_empty() {
237 return Err(AuthError::TokenInvalid(
238 "Refresh token is empty".to_string(),
239 ));
240 }
241
242 let claims = self.encoder.decode(refresh_token)?;
243 let now = current_timestamp_secs();
244 let new_exp = now + (self.expiration as i64);
245 let new_claims = JwtClaims::new(claims.sub, new_exp)
246 .with_issuer(self.issuer.clone())
247 .with_roles(claims.roles)
248 .with_permissions(claims.permissions)
249 .with_user_id(claims.user_id.unwrap_or(0));
250 let new_access_token = self.encoder.encode(&new_claims)?;
251
252 Ok(Token::new(new_access_token, self.expiration))
253 }
254}
255
256pub struct Claims {
258 pub sub: String,
259 pub exp: i64,
260 pub iat: i64,
261 pub roles: Vec<String>,
262 pub permissions: Vec<String>,
263}
264
265impl Claims {
266 pub fn new(subject: impl Into<String>) -> Self {
267 Self {
268 sub: subject.into(),
269 exp: 0,
270 iat: current_timestamp(),
271 roles: Vec::new(),
272 permissions: Vec::new(),
273 }
274 }
275
276 pub fn with_roles(mut self, roles: Vec<String>) -> Self {
277 self.roles = roles;
278 self
279 }
280
281 pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
282 self.permissions = permissions;
283 self
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_credentials_new() {
293 let creds = Credentials::new("user", "pass");
294 assert_eq!(creds.username, "user");
295 assert_eq!(creds.password, "pass");
296 }
297
298 #[test]
299 fn test_token_new() {
300 let token = Token::new("access_token", 3600);
301 assert_eq!(token.access_token, "access_token");
302 assert_eq!(token.expires_in, 3600);
303 assert_eq!(token.token_type, "Bearer");
304 assert!(token.refresh_token.is_none());
305 }
306
307 #[test]
308 fn test_token_with_refresh() {
309 let token = Token::new("access", 3600).with_refresh("refresh_token");
310 assert_eq!(token.refresh_token, Some("refresh_token".to_string()));
311 }
312
313 #[test]
314 fn test_token_is_expired() {
315 let mut token = Token::new("test", 1);
318 assert!(!token.is_expired());
319
320 token.issued_at = current_timestamp() - 100_000;
321 token.expires_in = 1;
322 assert!(token.is_expired());
323 }
324
325 #[test]
326 fn test_user_new() {
327 let user = User::new(1, "username");
328 assert_eq!(user.id, 1);
329 assert_eq!(user.username, "username");
330 assert!(user.email.is_none());
331 }
332
333 #[test]
334 fn test_user_with_email() {
335 let user = User::new(1, "user").with_email("user@test.com");
336 assert_eq!(user.email, Some("user@test.com".to_string()));
337 }
338
339 #[test]
340 fn test_user_with_roles() {
341 let user = User::new(1, "user").with_roles(vec!["admin".to_string(), "user".to_string()]);
342 assert!(user.has_role("admin"));
343 assert!(user.has_role("user"));
344 assert!(!user.has_role("guest"));
345 }
346
347 #[test]
348 fn test_user_has_permission() {
349 let user =
350 User::new(1, "user").with_permissions(vec!["read".to_string(), "write".to_string()]);
351
352 assert!(user.has_permission("read"));
353 assert!(user.has_permission("write"));
354 assert!(!user.has_permission("delete"));
355 }
356
357 #[test]
358 fn test_user_admin_has_all() {
359 let user = User::new(1, "admin").with_roles(vec!["admin".to_string()]);
360
361 assert!(user.has_permission("anything"));
362 assert!(user.has_permission("delete"));
363 }
364
365 #[test]
368 fn test_jwt_authenticate_issues_real_jwt() {
369 let auth = JwtAuthenticator::new("super-secret", "test-issuer", 3600);
370 let creds = Credentials::new("alice", "password123");
371
372 let token = auth.authenticate(&creds).expect("authenticate");
373 assert!(!token.access_token.is_empty());
374 assert_eq!(token.token_type, "Bearer");
375 assert_eq!(token.expires_in, 3600);
376 assert!(token.refresh_token.is_some());
377
378 let parts: Vec<&str> = token.access_token.split('.').collect();
380 assert_eq!(parts.len(), 3, "access token must be a 3-part JWT");
381 let refresh_parts: Vec<&str> = token.refresh_token.as_ref().unwrap().split('.').collect();
382 assert_eq!(refresh_parts.len(), 3, "refresh token must be a 3-part JWT");
383 }
384
385 #[test]
386 fn test_jwt_authenticate_rejects_empty_credentials() {
387 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
388 let creds = Credentials::new("", "");
389 let result = auth.authenticate(&creds);
390 assert!(matches!(result, Err(AuthError::InvalidCredentials(_))));
391 }
392
393 #[test]
394 fn test_jwt_verify_roundtrip() {
395 let auth = JwtAuthenticator::new("super-secret", "test-issuer", 3600);
396 let creds = Credentials::new("bob", "pw");
397
398 let token = auth.authenticate(&creds).expect("authenticate");
399 let user = auth.verify_token(&token.access_token).expect("verify");
400
401 assert_eq!(user.username, "bob");
402 assert!(user.has_role("user"));
403 }
404
405 #[test]
406 fn test_jwt_verify_rejects_garbage() {
407 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
408 let result = auth.verify_token("not.a.jwt");
409 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
410 }
411
412 #[test]
413 fn test_jwt_verify_rejects_empty() {
414 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
415 let result = auth.verify_token("");
416 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
417 }
418
419 #[test]
420 fn test_jwt_verify_rejects_wrong_secret() {
421 let auth_a = JwtAuthenticator::new("secret-a", "issuer", 3600);
422 let auth_b = JwtAuthenticator::new("secret-b", "issuer", 3600);
423
424 let token = auth_a
425 .authenticate(&Credentials::new("user", "pw"))
426 .unwrap();
427 let result = auth_b.verify_token(&token.access_token);
428 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
429 }
430
431 #[test]
432 fn test_jwt_refresh_roundtrip() {
433 let auth = JwtAuthenticator::new("super-secret", "issuer", 3600);
434 let token = auth.authenticate(&Credentials::new("carol", "pw")).unwrap();
435
436 let refreshed = auth
437 .refresh_token(token.refresh_token.as_ref().unwrap())
438 .expect("refresh");
439 let user = auth
441 .verify_token(&refreshed.access_token)
442 .expect("verify refreshed");
443 assert_eq!(user.username, "carol");
444 }
445
446 #[test]
447 fn test_jwt_refresh_rejects_empty() {
448 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
449 let result = auth.refresh_token("");
450 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
451 }
452
453 #[test]
454 fn test_jwt_refresh_rejects_garbage() {
455 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
456 let result = auth.refresh_token("garbage");
457 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
458 }
459
460 #[test]
461 fn test_jwt_claims_carried_to_user() {
462 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
463 let token = auth.authenticate(&Credentials::new("dave", "pw")).unwrap();
464 let user = auth.verify_token(&token.access_token).unwrap();
465 assert_eq!(user.roles, vec!["user".to_string()]);
467 }
468}