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 const MIN_SECRET_LEN: usize = 32;
167
168 pub fn new(secret: impl Into<String>, issuer: impl Into<String>, expiration: u64) -> Self {
173 Self {
174 encoder: JwtEncoder::new(secret),
175 issuer: issuer.into(),
176 expiration,
177 password_verifier: None,
178 }
179 }
180
181 pub fn try_new(
186 secret: impl Into<String>,
187 issuer: impl Into<String>,
188 expiration: u64,
189 ) -> Result<Self, AuthError> {
190 let secret = secret.into();
191 if secret.len() < Self::MIN_SECRET_LEN {
192 return Err(AuthError::SecretTooShort(format!(
193 "JWT secret must be at least {} bytes (got {})",
194 Self::MIN_SECRET_LEN,
195 secret.len()
196 )));
197 }
198 Ok(Self {
199 encoder: JwtEncoder::new(secret),
200 issuer: issuer.into(),
201 expiration,
202 password_verifier: None,
203 })
204 }
205
206 pub fn with_password_verifier(mut self, verifier: Arc<dyn PasswordVerifier>) -> Self {
211 self.password_verifier = Some(verifier);
212 self
213 }
214
215 pub fn authenticate(&self, credentials: &Credentials) -> Result<Token, AuthError> {
216 if credentials.username.is_empty() || credentials.password.is_empty() {
217 return Err(AuthError::InvalidCredentials(
218 "Username or password is empty".to_string(),
219 ));
220 }
221
222 let user_id: i64 = match &self.password_verifier {
227 Some(verifier) => {
228 verifier.verify_password(&credentials.username, &credentials.password)?
229 }
230 None => {
231 return Err(AuthError::Config(
232 "JwtAuthenticator.password_verifier not configured; \
233 call with_password_verifier() before authenticate() (H-1)"
234 .to_string(),
235 ));
236 }
237 };
238
239 let exp = current_timestamp_secs() + (self.expiration as i64);
240 let claims = JwtClaims::new(credentials.username.clone(), exp)
243 .with_issuer(self.issuer.clone())
244 .with_roles(vec!["user".to_string()])
245 .with_user_id(user_id)
246 .with_token_use("access");
247
248 let access_token = self.encoder.encode(&claims)?;
249 let refresh_claims = JwtClaims::new(credentials.username.clone(), exp + 86400)
250 .with_issuer(self.issuer.clone())
251 .with_user_id(user_id)
252 .with_token_use("refresh");
253 let refresh_token = self.encoder.encode(&refresh_claims)?;
254
255 Ok(Token::new(access_token, self.expiration).with_refresh(refresh_token))
256 }
257
258 pub fn verify_token(&self, token: &str) -> Result<User, AuthError> {
259 if token.is_empty() {
260 return Err(AuthError::TokenInvalid("Token is empty".to_string()));
261 }
262
263 let claims = self.encoder.decode(token)?;
264 let user_id = claims.user_id.unwrap_or_else(|| {
266 eprintln!(
267 "[warn] JwtAuthenticator::verify_token: token has no user_id claim \
268 (legacy token); falling back to user.id=0 (Critical S-2)"
269 );
270 0
271 });
272 let user = User::new(user_id, claims.sub.clone())
273 .with_roles(claims.roles.clone())
274 .with_permissions(claims.permissions.clone());
275 Ok(user)
276 }
277
278 pub fn refresh_token(&self, refresh_token: &str) -> Result<Token, AuthError> {
279 if refresh_token.is_empty() {
280 return Err(AuthError::TokenInvalid(
281 "Refresh token is empty".to_string(),
282 ));
283 }
284
285 let claims = self.encoder.decode(refresh_token)?;
286 if !claims.is_token_use("refresh") {
290 return Err(AuthError::TokenInvalid(
291 "Token is not a refresh token (missing token_use=refresh)".to_string(),
292 ));
293 }
294
295 let now = current_timestamp_secs();
296 let new_exp = now + (self.expiration as i64);
297 let new_claims = JwtClaims::new(claims.sub, new_exp)
298 .with_issuer(self.issuer.clone())
299 .with_roles(claims.roles)
300 .with_permissions(claims.permissions)
301 .with_user_id(claims.user_id.unwrap_or(0))
302 .with_token_use("access");
303 let new_access_token = self.encoder.encode(&new_claims)?;
304
305 Ok(Token::new(new_access_token, self.expiration))
306 }
307}
308
309pub struct Claims {
311 pub sub: String,
312 pub exp: i64,
313 pub iat: i64,
314 pub roles: Vec<String>,
315 pub permissions: Vec<String>,
316}
317
318impl Claims {
319 pub fn new(subject: impl Into<String>) -> Self {
320 Self {
321 sub: subject.into(),
322 exp: 0,
323 iat: current_timestamp(),
324 roles: Vec::new(),
325 permissions: Vec::new(),
326 }
327 }
328
329 pub fn with_roles(mut self, roles: Vec<String>) -> Self {
330 self.roles = roles;
331 self
332 }
333
334 pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
335 self.permissions = permissions;
336 self
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 struct MockPasswordVerifier;
349 impl PasswordVerifier for MockPasswordVerifier {
350 fn verify_password(&self, username: &str, _password: &str) -> Result<i64, AuthError> {
351 let id = (username.bytes().next().unwrap_or(b'x') as i64) + (username.len() as i64);
353 Ok(id)
354 }
355 }
356
357 fn auth_with_verifier(secret: &str, issuer: &str, exp: u64) -> JwtAuthenticator {
359 JwtAuthenticator::new(secret, issuer, exp)
360 .with_password_verifier(std::sync::Arc::new(MockPasswordVerifier))
361 }
362
363 #[test]
364 fn test_credentials_new() {
365 let creds = Credentials::new("user", "pass");
366 assert_eq!(creds.username, "user");
367 assert_eq!(creds.password, "pass");
368 }
369
370 #[test]
371 fn test_token_new() {
372 let token = Token::new("access_token", 3600);
373 assert_eq!(token.access_token, "access_token");
374 assert_eq!(token.expires_in, 3600);
375 assert_eq!(token.token_type, "Bearer");
376 assert!(token.refresh_token.is_none());
377 }
378
379 #[test]
380 fn test_token_with_refresh() {
381 let token = Token::new("access", 3600).with_refresh("refresh_token");
382 assert_eq!(token.refresh_token, Some("refresh_token".to_string()));
383 }
384
385 #[test]
386 fn test_token_is_expired() {
387 let mut token = Token::new("test", 1);
390 assert!(!token.is_expired());
391
392 token.issued_at = current_timestamp() - 100_000;
393 token.expires_in = 1;
394 assert!(token.is_expired());
395 }
396
397 #[test]
398 fn test_user_new() {
399 let user = User::new(1, "username");
400 assert_eq!(user.id, 1);
401 assert_eq!(user.username, "username");
402 assert!(user.email.is_none());
403 }
404
405 #[test]
406 fn test_user_with_email() {
407 let user = User::new(1, "user").with_email("user@test.com");
408 assert_eq!(user.email, Some("user@test.com".to_string()));
409 }
410
411 #[test]
412 fn test_user_with_roles() {
413 let user = User::new(1, "user").with_roles(vec!["admin".to_string(), "user".to_string()]);
414 assert!(user.has_role("admin"));
415 assert!(user.has_role("user"));
416 assert!(!user.has_role("guest"));
417 }
418
419 #[test]
420 fn test_user_has_permission() {
421 let user =
422 User::new(1, "user").with_permissions(vec!["read".to_string(), "write".to_string()]);
423
424 assert!(user.has_permission("read"));
425 assert!(user.has_permission("write"));
426 assert!(!user.has_permission("delete"));
427 }
428
429 #[test]
430 fn test_user_admin_has_all() {
431 let user = User::new(1, "admin").with_roles(vec!["admin".to_string()]);
432
433 assert!(user.has_permission("anything"));
434 assert!(user.has_permission("delete"));
435 }
436
437 #[test]
440 fn test_jwt_authenticate_issues_real_jwt() {
441 let auth = auth_with_verifier("super-secret", "test-issuer", 3600);
442 let creds = Credentials::new("alice", "password123");
443
444 let token = auth.authenticate(&creds).expect("authenticate");
445 assert!(!token.access_token.is_empty());
446 assert_eq!(token.token_type, "Bearer");
447 assert_eq!(token.expires_in, 3600);
448 assert!(token.refresh_token.is_some());
449
450 let parts: Vec<&str> = token.access_token.split('.').collect();
452 assert_eq!(parts.len(), 3, "access token must be a 3-part JWT");
453 let refresh_parts: Vec<&str> = token.refresh_token.as_ref().unwrap().split('.').collect();
454 assert_eq!(refresh_parts.len(), 3, "refresh token must be a 3-part JWT");
455 }
456
457 #[test]
458 fn test_jwt_authenticate_rejects_empty_credentials() {
459 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
460 let creds = Credentials::new("", "");
461 let result = auth.authenticate(&creds);
462 assert!(matches!(result, Err(AuthError::InvalidCredentials(_))));
463 }
464
465 #[test]
466 fn test_jwt_verify_roundtrip() {
467 let auth = auth_with_verifier("super-secret", "test-issuer", 3600);
468 let creds = Credentials::new("bob", "pw");
469
470 let token = auth.authenticate(&creds).expect("authenticate");
471 let user = auth.verify_token(&token.access_token).expect("verify");
472
473 assert_eq!(user.username, "bob");
474 assert!(user.has_role("user"));
475 }
476
477 #[test]
478 fn test_jwt_verify_rejects_garbage() {
479 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
480 let result = auth.verify_token("not.a.jwt");
481 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
482 }
483
484 #[test]
485 fn test_jwt_verify_rejects_empty() {
486 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
487 let result = auth.verify_token("");
488 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
489 }
490
491 #[test]
492 fn test_jwt_verify_rejects_wrong_secret() {
493 let auth_a = auth_with_verifier("secret-a", "issuer", 3600);
494 let auth_b = JwtAuthenticator::new("secret-b", "issuer", 3600);
495
496 let token = auth_a
497 .authenticate(&Credentials::new("user", "pw"))
498 .unwrap();
499 let result = auth_b.verify_token(&token.access_token);
500 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
501 }
502
503 #[test]
504 fn test_jwt_refresh_roundtrip() {
505 let auth = auth_with_verifier("super-secret", "issuer", 3600);
506 let token = auth.authenticate(&Credentials::new("carol", "pw")).unwrap();
507
508 let refreshed = auth
509 .refresh_token(token.refresh_token.as_ref().unwrap())
510 .expect("refresh");
511 let user = auth
513 .verify_token(&refreshed.access_token)
514 .expect("verify refreshed");
515 assert_eq!(user.username, "carol");
516 }
517
518 #[test]
519 fn test_jwt_refresh_rejects_empty() {
520 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
521 let result = auth.refresh_token("");
522 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
523 }
524
525 #[test]
526 fn test_jwt_refresh_rejects_garbage() {
527 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
528 let result = auth.refresh_token("garbage");
529 assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
530 }
531
532 #[test]
533 fn test_jwt_claims_carried_to_user() {
534 let auth = auth_with_verifier("secret", "issuer", 3600);
535 let token = auth.authenticate(&Credentials::new("dave", "pw")).unwrap();
536 let user = auth.verify_token(&token.access_token).unwrap();
537 assert_eq!(user.roles, vec!["user".to_string()]);
539 }
540
541 #[test]
543 fn test_jwt_authenticate_rejects_when_verifier_not_configured() {
544 let auth = JwtAuthenticator::new("secret", "issuer", 3600);
545 let creds = Credentials::new("alice", "password123");
546 let result = auth.authenticate(&creds);
547 assert!(
548 matches!(result, Err(AuthError::Config(_))),
549 "expected Err(AuthError::Config) when password_verifier is None, got {:?}",
550 result
551 );
552 }
553}