1use crate::http::security::config::Authenticator;
25use crate::http::security::User;
26use actix_web::dev::ServiceRequest;
27use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
28use serde::{Deserialize, Serialize};
29use std::time::{Duration, SystemTime, UNIX_EPOCH};
30
31pub use jsonwebtoken::Algorithm;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Claims {
44 pub sub: String,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub iss: Option<String>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub aud: Option<String>,
54
55 pub exp: u64,
57
58 pub iat: u64,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub nbf: Option<u64>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub jti: Option<String>,
68
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
71 pub roles: Vec<String>,
72
73 #[serde(default, skip_serializing_if = "Vec::is_empty")]
75 pub authorities: Vec<String>,
76
77 #[serde(flatten, skip_serializing_if = "Option::is_none")]
79 pub custom: Option<serde_json::Value>,
80}
81
82impl Claims {
83 pub fn new(username: &str, expiration_secs: u64) -> Self {
85 let now = SystemTime::now()
86 .duration_since(UNIX_EPOCH)
87 .unwrap()
88 .as_secs();
89
90 Self {
91 sub: username.to_string(),
92 iss: None,
93 aud: None,
94 exp: now + expiration_secs,
95 iat: now,
96 nbf: None,
97 jti: None,
98 roles: Vec::new(),
99 authorities: Vec::new(),
100 custom: None,
101 }
102 }
103
104 pub fn issuer(mut self, issuer: &str) -> Self {
106 self.iss = Some(issuer.to_string());
107 self
108 }
109
110 pub fn audience(mut self, audience: &str) -> Self {
112 self.aud = Some(audience.to_string());
113 self
114 }
115
116 pub fn roles(mut self, roles: Vec<String>) -> Self {
118 self.roles = roles;
119 self
120 }
121
122 pub fn authorities(mut self, authorities: Vec<String>) -> Self {
124 self.authorities = authorities;
125 self
126 }
127
128 pub fn custom(mut self, custom: serde_json::Value) -> Self {
130 self.custom = Some(custom);
131 self
132 }
133
134 pub fn from_user(user: &User, expiration_secs: u64) -> Self {
136 Self::new(user.get_username(), expiration_secs)
137 .roles(user.get_roles().to_vec())
138 .authorities(user.get_authorities().to_vec())
139 }
140}
141
142#[derive(Clone)]
158pub struct JwtConfig {
159 secret: String,
161 algorithm: Algorithm,
163 issuer: Option<String>,
165 audience: Option<String>,
167 expiration_secs: u64,
169 leeway_secs: u64,
171 header_prefix: String,
173 header_name: String,
175 validate_exp: bool,
177}
178
179impl JwtConfig {
180 pub fn new(secret: &str) -> Self {
185 Self {
186 secret: secret.to_string(),
187 algorithm: Algorithm::HS256,
188 issuer: None,
189 audience: None,
190 expiration_secs: 3600, leeway_secs: 0,
192 header_prefix: "Bearer ".to_string(),
193 header_name: "Authorization".to_string(),
194 validate_exp: true,
195 }
196 }
197
198 pub fn algorithm(mut self, algorithm: Algorithm) -> Self {
200 self.algorithm = algorithm;
201 self
202 }
203
204 pub fn issuer(mut self, issuer: &str) -> Self {
206 self.issuer = Some(issuer.to_string());
207 self
208 }
209
210 pub fn audience(mut self, audience: &str) -> Self {
212 self.audience = Some(audience.to_string());
213 self
214 }
215
216 pub fn expiration_secs(mut self, secs: u64) -> Self {
218 self.expiration_secs = secs;
219 self
220 }
221
222 pub fn expiration_hours(mut self, hours: u64) -> Self {
224 self.expiration_secs = hours * 3600;
225 self
226 }
227
228 pub fn expiration_days(mut self, days: u64) -> Self {
230 self.expiration_secs = days * 86400;
231 self
232 }
233
234 pub fn leeway_secs(mut self, secs: u64) -> Self {
236 self.leeway_secs = secs;
237 self
238 }
239
240 pub fn header_prefix(mut self, prefix: &str) -> Self {
242 self.header_prefix = prefix.to_string();
243 self
244 }
245
246 pub fn header_name(mut self, name: &str) -> Self {
248 self.header_name = name.to_string();
249 self
250 }
251
252 pub fn disable_exp_validation(mut self) -> Self {
254 self.validate_exp = false;
255 self
256 }
257
258 pub fn expiration_duration(&self) -> Duration {
260 Duration::from_secs(self.expiration_secs)
261 }
262}
263
264#[derive(Clone)]
291pub struct JwtAuthenticator {
292 config: JwtConfig,
293}
294
295impl JwtAuthenticator {
296 pub fn new(config: JwtConfig) -> Self {
298 Self { config }
299 }
300
301 pub fn config(&self) -> &JwtConfig {
303 &self.config
304 }
305
306 pub fn generate_token(&self, user: &User) -> Result<String, JwtError> {
314 let mut claims = Claims::from_user(user, self.config.expiration_secs);
315
316 if let Some(ref issuer) = self.config.issuer {
317 claims = claims.issuer(issuer);
318 }
319 if let Some(ref audience) = self.config.audience {
320 claims = claims.audience(audience);
321 }
322
323 let header = Header::new(self.config.algorithm);
324 let key = EncodingKey::from_secret(self.config.secret.as_bytes());
325
326 encode(&header, &claims, &key).map_err(JwtError::Encoding)
327 }
328
329 pub fn generate_token_with_claims(&self, claims: &Claims) -> Result<String, JwtError> {
331 let header = Header::new(self.config.algorithm);
332 let key = EncodingKey::from_secret(self.config.secret.as_bytes());
333
334 encode(&header, claims, &key).map_err(JwtError::Encoding)
335 }
336
337 pub fn validate_token(&self, token: &str) -> Result<TokenData<Claims>, JwtError> {
339 let key = DecodingKey::from_secret(self.config.secret.as_bytes());
340
341 let mut validation = Validation::new(self.config.algorithm);
342 validation.leeway = self.config.leeway_secs;
343 validation.validate_exp = self.config.validate_exp;
344
345 if let Some(ref issuer) = self.config.issuer {
346 validation.set_issuer(&[issuer]);
347 }
348 if let Some(ref audience) = self.config.audience {
349 validation.set_audience(&[audience]);
350 }
351
352 decode::<Claims>(token, &key, &validation).map_err(JwtError::Decoding)
353 }
354
355 fn extract_token(&self, req: &ServiceRequest) -> Option<String> {
357 let header_value = req.headers().get(&self.config.header_name)?;
358 let header_str = header_value.to_str().ok()?;
359
360 if header_str.starts_with(&self.config.header_prefix) {
361 Some(header_str[self.config.header_prefix.len()..].to_string())
362 } else {
363 None
364 }
365 }
366}
367
368impl Authenticator for JwtAuthenticator {
369 fn get_user(&self, req: &ServiceRequest) -> Option<User> {
370 let token = self.extract_token(req)?;
372
373 let token_data = self.validate_token(&token).ok()?;
375 let claims = token_data.claims;
376
377 let roles: Vec<String> = claims.roles;
379 let authorities: Vec<String> = claims.authorities;
380
381 Some(
382 User::new(claims.sub, String::new())
383 .roles(&roles)
384 .authorities(&authorities),
385 )
386 }
387}
388
389#[derive(Debug)]
395pub enum JwtError {
396 Encoding(jsonwebtoken::errors::Error),
398 Decoding(jsonwebtoken::errors::Error),
400 Expired,
402 InvalidFormat,
404}
405
406impl std::fmt::Display for JwtError {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 match self {
409 JwtError::Encoding(e) => write!(f, "JWT encoding error: {}", e),
410 JwtError::Decoding(e) => write!(f, "JWT decoding error: {}", e),
411 JwtError::Expired => write!(f, "JWT token expired"),
412 JwtError::InvalidFormat => write!(f, "Invalid JWT format"),
413 }
414 }
415}
416
417impl std::error::Error for JwtError {}
418
419#[derive(Clone)]
436pub struct JwtTokenService {
437 config: JwtConfig,
438 refresh_expiration_secs: u64,
439}
440
441impl JwtTokenService {
442 pub fn new(config: JwtConfig) -> Self {
444 Self {
445 refresh_expiration_secs: config.expiration_secs * 24, config,
447 }
448 }
449
450 pub fn refresh_expiration_days(mut self, days: u64) -> Self {
452 self.refresh_expiration_secs = days * 86400;
453 self
454 }
455
456 pub fn generate_token(&self, user: &User) -> Result<String, JwtError> {
458 let authenticator = JwtAuthenticator::new(self.config.clone());
459 authenticator.generate_token(user)
460 }
461
462 pub fn generate_refresh_token(&self, user: &User) -> Result<String, JwtError> {
464 let claims = Claims::new(user.get_username(), self.refresh_expiration_secs);
465 let header = Header::new(self.config.algorithm);
466 let key = EncodingKey::from_secret(self.config.secret.as_bytes());
467
468 encode(&header, &claims, &key).map_err(JwtError::Encoding)
469 }
470
471 pub fn validate_token(&self, token: &str) -> Result<Claims, JwtError> {
473 let authenticator = JwtAuthenticator::new(self.config.clone());
474 authenticator.validate_token(token).map(|td| td.claims)
475 }
476
477 pub fn config(&self) -> &JwtConfig {
479 &self.config
480 }
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct TokenPair {
493 pub access_token: String,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub refresh_token: Option<String>,
498 pub token_type: String,
500 pub expires_in: u64,
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub refresh_expires_in: Option<u64>,
505}
506
507impl TokenPair {
508 pub fn new(access_token: String, expires_in: u64) -> Self {
510 Self {
511 access_token,
512 refresh_token: None,
513 token_type: "Bearer".to_string(),
514 expires_in,
515 refresh_expires_in: None,
516 }
517 }
518
519 pub fn with_refresh_token(mut self, refresh_token: String, refresh_expires_in: u64) -> Self {
521 self.refresh_token = Some(refresh_token);
522 self.refresh_expires_in = Some(refresh_expires_in);
523 self
524 }
525}
526
527impl JwtTokenService {
528 pub fn generate_token_pair(&self, user: &User) -> Result<TokenPair, JwtError> {
537 let access_token = self.generate_token(user)?;
538 let refresh_token = self.generate_refresh_token(user)?;
539
540 Ok(TokenPair::new(access_token, self.config.expiration_secs)
541 .with_refresh_token(refresh_token, self.refresh_expiration_secs))
542 }
543
544 pub fn refresh_tokens(&self, refresh_token: &str) -> Result<TokenPair, JwtError> {
548 let claims = self.validate_token(refresh_token)?;
550
551 let user = User::new(claims.sub, String::new())
553 .roles(&claims.roles)
554 .authorities(&claims.authorities);
555
556 self.generate_token_pair(&user)
558 }
559}
560
561pub trait ClaimsExtractor: Send + Sync {
582 fn extract_user(&self, claims: &Claims) -> Option<User>;
584}
585
586#[derive(Clone, Default)]
588pub struct DefaultClaimsExtractor {
589 username_claim: Option<String>,
591 roles_claim: Option<String>,
593 authorities_claim: Option<String>,
595}
596
597impl DefaultClaimsExtractor {
598 pub fn new() -> Self {
600 Self::default()
601 }
602
603 pub fn username_claim(mut self, claim: &str) -> Self {
605 self.username_claim = Some(claim.to_string());
606 self
607 }
608
609 pub fn roles_claim(mut self, claim: &str) -> Self {
611 self.roles_claim = Some(claim.to_string());
612 self
613 }
614
615 pub fn authorities_claim(mut self, claim: &str) -> Self {
617 self.authorities_claim = Some(claim.to_string());
618 self
619 }
620}
621
622impl ClaimsExtractor for DefaultClaimsExtractor {
623 fn extract_user(&self, claims: &Claims) -> Option<User> {
624 let username = claims.sub.clone();
625 let roles = claims.roles.clone();
626 let authorities = claims.authorities.clone();
627
628 Some(
629 User::new(username, String::new())
630 .roles(&roles)
631 .authorities(&authorities),
632 )
633 }
634}
635
636impl JwtConfig {
641 pub fn with_rsa_public_key(public_key_pem: &str) -> Self {
650 Self {
651 secret: public_key_pem.to_string(),
652 algorithm: Algorithm::RS256,
653 issuer: None,
654 audience: None,
655 expiration_secs: 3600,
656 leeway_secs: 0,
657 header_prefix: "Bearer ".to_string(),
658 header_name: "Authorization".to_string(),
659 validate_exp: true,
660 }
661 }
662
663 pub fn is_rsa(&self) -> bool {
665 matches!(
666 self.algorithm,
667 Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512
668 )
669 }
670
671 pub fn is_hmac(&self) -> bool {
673 matches!(
674 self.algorithm,
675 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512
676 )
677 }
678}
679
680impl JwtAuthenticator {
682 pub fn validate_token_rsa(&self, token: &str) -> Result<TokenData<Claims>, JwtError> {
686 if !self.config.is_rsa() {
687 return self.validate_token(token);
688 }
689
690 let key =
691 DecodingKey::from_rsa_pem(self.config.secret.as_bytes()).map_err(JwtError::Decoding)?;
692
693 let mut validation = Validation::new(self.config.algorithm);
694 validation.leeway = self.config.leeway_secs;
695 validation.validate_exp = self.config.validate_exp;
696
697 if let Some(ref issuer) = self.config.issuer {
698 validation.set_issuer(&[issuer]);
699 }
700 if let Some(ref audience) = self.config.audience {
701 validation.set_audience(&[audience]);
702 }
703
704 decode::<Claims>(token, &key, &validation).map_err(JwtError::Decoding)
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 fn test_user() -> User {
713 User::new("testuser".to_string(), "password".to_string())
714 .roles(&["USER".into(), "ADMIN".into()])
715 .authorities(&["read".into(), "write".into()])
716 }
717
718 #[test]
719 fn test_generate_and_validate_token() {
720 let config = JwtConfig::new("super-secret-key-that-is-long-enough")
721 .issuer("test-app")
722 .expiration_hours(1);
723
724 let authenticator = JwtAuthenticator::new(config);
725 let user = test_user();
726
727 let token = authenticator.generate_token(&user).unwrap();
729 assert!(!token.is_empty());
730
731 let token_data = authenticator.validate_token(&token).unwrap();
733 assert_eq!(token_data.claims.sub, "testuser");
734 assert!(token_data.claims.roles.contains(&"USER".to_string()));
735 assert!(token_data.claims.roles.contains(&"ADMIN".to_string()));
736 assert!(token_data.claims.authorities.contains(&"read".to_string()));
737 }
738
739 #[test]
740 fn test_invalid_token() {
741 let config = JwtConfig::new("super-secret-key-that-is-long-enough");
742 let authenticator = JwtAuthenticator::new(config);
743
744 let result = authenticator.validate_token("invalid-token");
745 assert!(result.is_err());
746 }
747
748 #[test]
749 fn test_wrong_secret() {
750 let config1 = JwtConfig::new("secret-key-one-that-is-long-enough");
751 let config2 = JwtConfig::new("secret-key-two-that-is-long-enough");
752
753 let auth1 = JwtAuthenticator::new(config1);
754 let auth2 = JwtAuthenticator::new(config2);
755
756 let token = auth1.generate_token(&test_user()).unwrap();
757 let result = auth2.validate_token(&token);
758 assert!(result.is_err());
759 }
760
761 #[test]
762 fn test_claims_from_user() {
763 let user = test_user();
764 let claims = Claims::from_user(&user, 3600);
765
766 assert_eq!(claims.sub, "testuser");
767 assert!(claims.roles.contains(&"USER".to_string()));
768 assert!(claims.authorities.contains(&"read".to_string()));
769 }
770
771 #[test]
772 fn test_token_service() {
773 let config = JwtConfig::new("super-secret-key-that-is-long-enough").expiration_hours(1);
774
775 let service = JwtTokenService::new(config).refresh_expiration_days(7);
776 let user = test_user();
777
778 let access_token = service.generate_token(&user).unwrap();
779 let refresh_token = service.generate_refresh_token(&user).unwrap();
780
781 assert!(!access_token.is_empty());
782 assert!(!refresh_token.is_empty());
783 assert_ne!(access_token, refresh_token);
784
785 let claims = service.validate_token(&access_token).unwrap();
787 assert!(!claims.roles.is_empty());
788
789 let refresh_claims = service.validate_token(&refresh_token).unwrap();
791 assert!(refresh_claims.roles.is_empty());
792 }
793}