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