1use crate::types::{
16 AuthError, Claims, JwtAlgorithm, JwtConfig, JwtKeyConfig, Result, TokenValidation, User,
17};
18use chrono::{DateTime, Duration, Utc};
19use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
20
21pub struct JwtManager {
23 encoding_key: Option<EncodingKey>,
24 decoding_key: DecodingKey,
25 algorithm: Algorithm,
26 issuer: String,
27 audience: String,
28 expiration_secs: u64,
29 refresh_expiration_secs: u64,
30 include_jti: bool,
31}
32
33impl JwtManager {
34 pub fn new(config: &JwtConfig) -> Result<Self> {
36 let algorithm = Self::convert_algorithm(config.algorithm);
37
38 let (encoding_key, decoding_key) = Self::create_keys(config)?;
39
40 Ok(Self {
41 encoding_key,
42 decoding_key,
43 algorithm,
44 issuer: config.issuer.clone(),
45 audience: config.audience.clone(),
46 expiration_secs: config.expiration_secs,
47 refresh_expiration_secs: config.refresh_expiration_secs,
48 include_jti: config.include_jti,
49 })
50 }
51
52 fn convert_algorithm(alg: JwtAlgorithm) -> Algorithm {
54 match alg {
55 JwtAlgorithm::HS256 => Algorithm::HS256,
56 JwtAlgorithm::HS384 => Algorithm::HS384,
57 JwtAlgorithm::HS512 => Algorithm::HS512,
58 JwtAlgorithm::RS256 => Algorithm::RS256,
59 JwtAlgorithm::RS384 => Algorithm::RS384,
60 JwtAlgorithm::RS512 => Algorithm::RS512,
61 JwtAlgorithm::ES256 => Algorithm::ES256,
62 JwtAlgorithm::ES384 => Algorithm::ES384,
63 }
64 }
65
66 fn create_keys(config: &JwtConfig) -> Result<(Option<EncodingKey>, DecodingKey)> {
68 match &config.key_config {
69 Some(JwtKeyConfig::Rsa {
70 private_key,
71 public_key,
72 }) => {
73 let encoding_key = if let Some(pk) = private_key {
74 Some(EncodingKey::from_rsa_pem(pk.as_bytes()).map_err(|e| {
75 AuthError::ConfigurationError(format!("Invalid RSA private key: {e}"))
76 })?)
77 } else {
78 None
79 };
80
81 let decoding_key =
82 DecodingKey::from_rsa_pem(public_key.as_bytes()).map_err(|e| {
83 AuthError::ConfigurationError(format!("Invalid RSA public key: {e}"))
84 })?;
85
86 Ok((encoding_key, decoding_key))
87 }
88 Some(JwtKeyConfig::Ec {
89 private_key,
90 public_key,
91 }) => {
92 let encoding_key = if let Some(pk) = private_key {
93 Some(EncodingKey::from_ec_pem(pk.as_bytes()).map_err(|e| {
94 AuthError::ConfigurationError(format!("Invalid EC private key: {e}"))
95 })?)
96 } else {
97 None
98 };
99
100 let decoding_key =
101 DecodingKey::from_ec_pem(public_key.as_bytes()).map_err(|e| {
102 AuthError::ConfigurationError(format!("Invalid EC public key: {e}"))
103 })?;
104
105 Ok((encoding_key, decoding_key))
106 }
107 Some(JwtKeyConfig::Secret(secret)) => {
108 let encoding_key = EncodingKey::from_secret(secret.as_bytes());
109 let decoding_key = DecodingKey::from_secret(secret.as_bytes());
110 Ok((Some(encoding_key), decoding_key))
111 }
112 None => {
113 let encoding_key = EncodingKey::from_secret(config.secret.as_bytes());
115 let decoding_key = DecodingKey::from_secret(config.secret.as_bytes());
116 Ok((Some(encoding_key), decoding_key))
117 }
118 }
119 }
120
121 pub fn generate_token(&self, user: &User) -> Result<String> {
123 let encoding_key = self.encoding_key.as_ref().ok_or_else(|| {
124 AuthError::ConfigurationError(
125 "No encoding key available (verification only mode)".into(),
126 )
127 })?;
128
129 let now = Utc::now();
130 let expiration = now + Duration::seconds(self.expiration_secs as i64);
131
132 let jti = if self.include_jti {
133 Some(uuid::Uuid::new_v4().to_string())
134 } else {
135 None
136 };
137
138 let claims = Claims {
139 sub: user.username.clone(),
140 exp: expiration.timestamp(),
141 iat: now.timestamp(),
142 nbf: now.timestamp(),
143 iss: self.issuer.clone(),
144 aud: self.audience.clone(),
145 roles: user.roles.clone(),
146 permissions: user.permissions.clone(),
147 jti,
148 email: user.email.clone(),
149 token_type: Some("access".to_string()),
150 };
151
152 encode(&Header::new(self.algorithm), &claims, encoding_key)
153 .map_err(|e| AuthError::InternalError(format!("Failed to generate JWT token: {e}")))
154 }
155
156 pub fn generate_token_with_claims(&self, claims: &Claims) -> Result<String> {
158 let encoding_key = self.encoding_key.as_ref().ok_or_else(|| {
159 AuthError::ConfigurationError(
160 "No encoding key available (verification only mode)".into(),
161 )
162 })?;
163
164 encode(&Header::new(self.algorithm), claims, encoding_key)
165 .map_err(|e| AuthError::InternalError(format!("Failed to generate JWT token: {e}")))
166 }
167
168 pub fn validate_token(&self, token: &str) -> Result<TokenValidation> {
170 let mut validation = Validation::new(self.algorithm);
171 validation.set_issuer(std::slice::from_ref(&self.issuer));
172 validation.set_audience(std::slice::from_ref(&self.audience));
173
174 let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
175 .map_err(|_e| AuthError::InvalidToken("Failed to decode token".into()))?;
176
177 let claims = token_data.claims;
178
179 let exp_time = DateTime::from_timestamp(claims.exp, 0).ok_or(AuthError::InvalidToken(
181 "Invalid expiration timestamp".into(),
182 ))?;
183
184 if Utc::now() > exp_time {
185 return Err(AuthError::TokenExpired);
186 }
187
188 let user = User {
189 username: claims.sub,
190 roles: claims.roles,
191 email: claims.email,
192 full_name: None,
193 last_login: None,
194 permissions: claims.permissions,
195 };
196
197 Ok(TokenValidation {
198 user,
199 expires_at: exp_time,
200 })
201 }
202
203 pub fn validate_token_claims(&self, token: &str) -> Result<Claims> {
205 let mut validation = Validation::new(self.algorithm);
206 validation.set_issuer(std::slice::from_ref(&self.issuer));
207 validation.set_audience(std::slice::from_ref(&self.audience));
208
209 let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
210 .map_err(|_e| AuthError::InvalidToken("Failed to decode token claims".into()))?;
211
212 let claims = token_data.claims;
213
214 let exp_time = DateTime::from_timestamp(claims.exp, 0).ok_or(AuthError::InvalidToken(
216 "Invalid expiration timestamp in claims".into(),
217 ))?;
218
219 if Utc::now() > exp_time {
220 return Err(AuthError::TokenExpired);
221 }
222
223 Ok(claims)
224 }
225
226 pub fn get_token_id(&self, token: &str) -> Result<Option<String>> {
228 let mut validation = Validation::new(self.algorithm);
230 validation.set_issuer(std::slice::from_ref(&self.issuer));
231 validation.set_audience(std::slice::from_ref(&self.audience));
232 validation.validate_exp = false; let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
235 .map_err(|_e| AuthError::InvalidToken("Failed to extract token ID".into()))?;
236
237 Ok(token_data.claims.jti)
238 }
239
240 #[must_use]
242 pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
243 auth_header.strip_prefix("Bearer ")
244 }
245
246 pub fn generate_refresh_token(&self, user: &User) -> Result<String> {
248 let encoding_key = self.encoding_key.as_ref().ok_or_else(|| {
249 AuthError::ConfigurationError(
250 "No encoding key available (verification only mode)".into(),
251 )
252 })?;
253
254 let now = Utc::now();
255 let expiration = now + Duration::seconds(self.refresh_expiration_secs as i64);
256
257 let jti = if self.include_jti {
258 Some(uuid::Uuid::new_v4().to_string())
259 } else {
260 None
261 };
262
263 let claims = Claims {
264 sub: user.username.clone(),
265 exp: expiration.timestamp(),
266 iat: now.timestamp(),
267 nbf: now.timestamp(),
268 iss: self.issuer.clone(),
269 aud: format!("{}-refresh", self.audience),
270 roles: user.roles.clone(),
271 permissions: user.permissions.clone(),
272 jti,
273 email: user.email.clone(),
274 token_type: Some("refresh".to_string()),
275 };
276
277 encode(&Header::new(self.algorithm), &claims, encoding_key)
278 .map_err(|e| AuthError::InternalError(format!("Failed to generate refresh token: {e}")))
279 }
280
281 pub fn validate_refresh_token(&self, token: &str) -> Result<TokenValidation> {
283 let mut validation = Validation::new(self.algorithm);
284 validation.set_issuer(std::slice::from_ref(&self.issuer));
285 let audience = format!("{}-refresh", self.audience);
286 validation.set_audience(std::slice::from_ref(&audience));
287
288 let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
289 .map_err(|_e| AuthError::InvalidToken("Failed to decode refresh token".into()))?;
290
291 let claims = token_data.claims;
292
293 let exp_time = DateTime::from_timestamp(claims.exp, 0).ok_or(AuthError::InvalidToken(
294 "Invalid expiration timestamp in refresh token".into(),
295 ))?;
296
297 if Utc::now() > exp_time {
298 return Err(AuthError::TokenExpired);
299 }
300
301 let user = User {
302 username: claims.sub,
303 roles: claims.roles,
304 email: claims.email,
305 full_name: None,
306 last_login: None,
307 permissions: claims.permissions,
308 };
309
310 Ok(TokenValidation {
311 user,
312 expires_at: exp_time,
313 })
314 }
315
316 pub fn get_token_expiration(&self, token: &str) -> Result<DateTime<Utc>> {
318 let mut validation = Validation::new(self.algorithm);
320 validation.set_issuer(std::slice::from_ref(&self.issuer));
321 validation.set_audience(std::slice::from_ref(&self.audience));
322 validation.validate_exp = false; let token_data =
325 decode::<Claims>(token, &self.decoding_key, &validation).map_err(|_e| {
326 AuthError::InvalidToken("Failed to decode token for expiration check".into())
327 })?;
328
329 DateTime::from_timestamp(token_data.claims.exp, 0).ok_or(AuthError::InvalidToken(
330 "Invalid expiration timestamp for expiration check".into(),
331 ))
332 }
333
334 pub fn is_token_close_to_expiration(&self, token: &str) -> Result<bool> {
336 let expiration = self.get_token_expiration(token)?;
337 let one_hour_from_now = Utc::now() + Duration::hours(1);
338 Ok(expiration <= one_hour_from_now)
339 }
340
341 #[must_use]
343 pub fn issuer(&self) -> &str {
344 &self.issuer
345 }
346
347 #[must_use]
349 pub fn audience(&self) -> &str {
350 &self.audience
351 }
352
353 #[must_use]
355 pub fn can_sign(&self) -> bool {
356 self.encoding_key.is_some()
357 }
358}
359
360pub struct ClaimsBuilder {
362 sub: String,
363 roles: Vec<String>,
364 permissions: Vec<crate::types::Permission>,
365 exp: i64,
366 iat: i64,
367 nbf: i64,
368 iss: String,
369 aud: String,
370 jti: Option<String>,
371 email: Option<String>,
372 token_type: Option<String>,
373}
374
375impl ClaimsBuilder {
376 pub fn new(
378 subject: impl Into<String>,
379 issuer: impl Into<String>,
380 audience: impl Into<String>,
381 ) -> Self {
382 let now = Utc::now().timestamp();
383 Self {
384 sub: subject.into(),
385 roles: Vec::new(),
386 permissions: Vec::new(),
387 exp: now + 3600, iat: now,
389 nbf: now,
390 iss: issuer.into(),
391 aud: audience.into(),
392 jti: None,
393 email: None,
394 token_type: None,
395 }
396 }
397
398 #[must_use]
400 pub fn roles(mut self, roles: Vec<String>) -> Self {
401 self.roles = roles;
402 self
403 }
404
405 #[must_use]
407 pub fn permissions(mut self, permissions: Vec<crate::types::Permission>) -> Self {
408 self.permissions = permissions;
409 self
410 }
411
412 #[must_use]
414 pub fn expires_in(mut self, duration: Duration) -> Self {
415 self.exp = (Utc::now() + duration).timestamp();
416 self
417 }
418
419 #[must_use]
421 pub fn expires_at(mut self, time: DateTime<Utc>) -> Self {
422 self.exp = time.timestamp();
423 self
424 }
425
426 #[must_use]
428 pub fn jti(mut self, jti: impl Into<String>) -> Self {
429 self.jti = Some(jti.into());
430 self
431 }
432
433 #[must_use]
435 pub fn with_random_jti(mut self) -> Self {
436 self.jti = Some(uuid::Uuid::new_v4().to_string());
437 self
438 }
439
440 #[must_use]
442 pub fn email(mut self, email: impl Into<String>) -> Self {
443 self.email = Some(email.into());
444 self
445 }
446
447 #[must_use]
449 pub fn token_type(mut self, token_type: impl Into<String>) -> Self {
450 self.token_type = Some(token_type.into());
451 self
452 }
453
454 #[must_use]
456 pub fn build(self) -> Claims {
457 Claims {
458 sub: self.sub,
459 roles: self.roles,
460 permissions: self.permissions,
461 exp: self.exp,
462 iat: self.iat,
463 nbf: self.nbf,
464 iss: self.iss,
465 aud: self.aud,
466 jti: self.jti,
467 email: self.email,
468 token_type: self.token_type,
469 }
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use crate::types::Permission;
477
478 #[test]
479 fn test_jwt_token_generation() {
480 let config = JwtConfig::development();
481 let manager = JwtManager::new(&config).unwrap();
482
483 let user = User {
484 username: "alice".to_string(),
485 roles: vec!["admin".to_string()],
486 email: Some("alice@example.com".to_string()),
487 full_name: Some("Alice Wonderland".to_string()),
488 last_login: None,
489 permissions: vec![Permission::Admin],
490 };
491
492 let token = manager.generate_token(&user).unwrap();
493 assert!(!token.is_empty());
494
495 let validation = manager.validate_token(&token).unwrap();
497 assert_eq!(validation.user.username, "alice");
498 assert_eq!(validation.user.roles.len(), 1);
499 }
500
501 #[test]
502 fn test_jwt_with_jti() {
503 let config = JwtConfig::development().with_jti(true);
504 let manager = JwtManager::new(&config).unwrap();
505
506 let user = User {
507 username: "alice".to_string(),
508 roles: vec!["admin".to_string()],
509 email: None,
510 full_name: None,
511 last_login: None,
512 permissions: vec![],
513 };
514
515 let token = manager.generate_token(&user).unwrap();
516
517 let jti = manager.get_token_id(&token).unwrap();
519 assert!(jti.is_some());
520
521 let claims = manager.validate_token_claims(&token).unwrap();
523 assert!(claims.jti.is_some());
524 assert_eq!(claims.token_type, Some("access".to_string()));
525 }
526
527 #[test]
528 fn test_extract_token_from_header() {
529 let header = "Bearer abc123";
530 let token = JwtManager::extract_token_from_header(header);
531 assert_eq!(token, Some("abc123"));
532
533 let invalid_header = "Basic abc123";
534 let token = JwtManager::extract_token_from_header(invalid_header);
535 assert_eq!(token, None);
536 }
537
538 #[test]
539 fn test_refresh_token() {
540 let config = JwtConfig::development();
541 let manager = JwtManager::new(&config).unwrap();
542
543 let user = User {
544 username: "bob".to_string(),
545 roles: vec!["user".to_string()],
546 email: None,
547 full_name: None,
548 last_login: None,
549 permissions: vec![Permission::Read],
550 };
551
552 let refresh_token = manager.generate_refresh_token(&user).unwrap();
553 assert!(!refresh_token.is_empty());
554
555 let validation = manager.validate_refresh_token(&refresh_token).unwrap();
557 assert_eq!(validation.user.username, "bob");
558 }
559
560 #[test]
561 fn test_invalid_token() {
562 let config = JwtConfig::development();
563 let manager = JwtManager::new(&config).unwrap();
564
565 let invalid_token = "invalid.token.here";
566 let result = manager.validate_token(invalid_token);
567 assert!(result.is_err());
568 }
569
570 #[test]
571 fn test_claims_builder() {
572 let claims = ClaimsBuilder::new("user123", "issuer", "audience")
573 .roles(vec!["admin".to_string()])
574 .permissions(vec![Permission::Admin])
575 .expires_in(Duration::hours(2))
576 .with_random_jti()
577 .email("user@example.com")
578 .token_type("access")
579 .build();
580
581 assert_eq!(claims.sub, "user123");
582 assert_eq!(claims.iss, "issuer");
583 assert_eq!(claims.aud, "audience");
584 assert!(claims.jti.is_some());
585 assert_eq!(claims.email, Some("user@example.com".to_string()));
586 }
587
588 #[test]
589 fn test_custom_claims_token() {
590 let config = JwtConfig::development();
591 let manager = JwtManager::new(&config).unwrap();
592
593 let claims = ClaimsBuilder::new("user123", &config.issuer, &config.audience)
594 .roles(vec!["custom".to_string()])
595 .expires_in(Duration::minutes(30))
596 .build();
597
598 let token = manager.generate_token_with_claims(&claims).unwrap();
599 assert!(!token.is_empty());
600
601 let validated = manager.validate_token_claims(&token).unwrap();
602 assert_eq!(validated.sub, "user123");
603 assert_eq!(validated.roles, vec!["custom".to_string()]);
604 }
605
606 #[test]
607 fn test_algorithm_selection() {
608 let config = JwtConfig::new("secret", "issuer", "audience", 3600)
610 .with_algorithm(JwtAlgorithm::HS384);
611 let manager = JwtManager::new(&config).unwrap();
612 assert!(manager.can_sign());
613
614 let config = JwtConfig::new("secret", "issuer", "audience", 3600)
616 .with_algorithm(JwtAlgorithm::HS512);
617 let manager = JwtManager::new(&config).unwrap();
618
619 let user = User {
620 username: "test".to_string(),
621 roles: vec![],
622 email: None,
623 full_name: None,
624 last_login: None,
625 permissions: vec![],
626 };
627
628 let token = manager.generate_token(&user).unwrap();
629 let validation = manager.validate_token(&token).unwrap();
630 assert_eq!(validation.user.username, "test");
631 }
632}