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