1use std::fmt;
56
57use chrono::{DateTime, Utc};
58use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};
59use serde::{Deserialize, Serialize};
60use zeroize::Zeroizing;
61
62use crate::security::errors::{Result, SecurityError};
63
64#[derive(Debug, Clone)]
72pub enum SigningKey {
73 Hs256(Zeroizing<Vec<u8>>),
78
79 Hs384(Zeroizing<Vec<u8>>),
81
82 Hs512(Zeroizing<Vec<u8>>),
84
85 Rs256Pem(String),
90
91 Rs384Pem(String),
93
94 Rs512Pem(String),
96
97 Rs256Components {
101 n: String,
103 e: String,
105 },
106}
107
108impl SigningKey {
109 #[must_use]
111 pub fn hs256(secret: &str) -> Self {
112 Self::Hs256(Zeroizing::new(secret.as_bytes().to_vec()))
113 }
114
115 #[must_use]
117 pub fn hs256_bytes(secret: &[u8]) -> Self {
118 Self::Hs256(Zeroizing::new(secret.to_vec()))
119 }
120
121 #[must_use]
123 pub fn rs256_pem(pem: &str) -> Self {
124 Self::Rs256Pem(pem.to_string())
125 }
126
127 #[must_use]
131 pub fn rs256_components(n: &str, e: &str) -> Self {
132 Self::Rs256Components {
133 n: n.to_string(),
134 e: e.to_string(),
135 }
136 }
137
138 #[must_use]
140 pub const fn algorithm(&self) -> Algorithm {
141 match self {
142 Self::Hs256(_) => Algorithm::HS256,
143 Self::Hs384(_) => Algorithm::HS384,
144 Self::Hs512(_) => Algorithm::HS512,
145 Self::Rs256Pem(_) | Self::Rs256Components { .. } => Algorithm::RS256,
146 Self::Rs384Pem(_) => Algorithm::RS384,
147 Self::Rs512Pem(_) => Algorithm::RS512,
148 }
149 }
150
151 fn to_decoding_key(&self) -> std::result::Result<DecodingKey, SecurityError> {
153 match self {
154 Self::Hs256(secret) | Self::Hs384(secret) | Self::Hs512(secret) => {
155 Ok(DecodingKey::from_secret(secret))
156 },
157 Self::Rs256Pem(pem) | Self::Rs384Pem(pem) | Self::Rs512Pem(pem) => {
158 DecodingKey::from_rsa_pem(pem.as_bytes()).map_err(|e| {
159 SecurityError::SecurityConfigError(format!("Invalid RSA PEM key: {e}"))
160 })
161 },
162 Self::Rs256Components { n, e } => DecodingKey::from_rsa_components(n, e).map_err(|e| {
163 SecurityError::SecurityConfigError(format!("Invalid RSA components: {e}"))
164 }),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct AuthConfig {
178 pub required: bool,
180
181 pub token_expiry_secs: u64,
183
184 #[serde(skip)]
189 pub signing_key: Option<SigningKey>,
190
191 #[serde(default)]
195 pub issuer: Option<String>,
196
197 #[serde(default)]
201 pub audience: Option<String>,
202
203 #[serde(default = "default_clock_skew")]
208 pub clock_skew_secs: u64,
209}
210
211fn default_clock_skew() -> u64 {
212 60
213}
214
215impl AuthConfig {
216 #[must_use]
222 pub fn permissive() -> Self {
223 Self {
224 required: false,
225 token_expiry_secs: 3600,
226 signing_key: None,
227 issuer: None,
228 audience: None,
229 clock_skew_secs: default_clock_skew(),
230 }
231 }
232
233 #[must_use]
239 pub fn standard() -> Self {
240 Self {
241 required: true,
242 token_expiry_secs: 3600,
243 signing_key: None,
244 issuer: None,
245 audience: None,
246 clock_skew_secs: default_clock_skew(),
247 }
248 }
249
250 #[must_use]
256 pub fn strict() -> Self {
257 Self {
258 required: true,
259 token_expiry_secs: 1800,
260 signing_key: None,
261 issuer: None,
262 audience: None,
263 clock_skew_secs: default_clock_skew(),
264 }
265 }
266
267 #[must_use]
272 pub fn with_hs256(secret: &str) -> Self {
273 Self {
274 required: true,
275 token_expiry_secs: 3600,
276 signing_key: Some(SigningKey::hs256(secret)),
277 issuer: None,
278 audience: None,
279 clock_skew_secs: default_clock_skew(),
280 }
281 }
282
283 #[must_use]
288 pub fn with_rs256_pem(pem: &str) -> Self {
289 Self {
290 required: true,
291 token_expiry_secs: 3600,
292 signing_key: Some(SigningKey::rs256_pem(pem)),
293 issuer: None,
294 audience: None,
295 clock_skew_secs: default_clock_skew(),
296 }
297 }
298
299 #[must_use]
301 pub fn with_issuer(mut self, issuer: &str) -> Self {
302 self.issuer = Some(issuer.to_string());
303 self
304 }
305
306 #[must_use]
308 pub fn with_audience(mut self, audience: &str) -> Self {
309 self.audience = Some(audience.to_string());
310 self
311 }
312
313 #[must_use]
315 pub const fn has_signing_key(&self) -> bool {
316 self.signing_key.is_some()
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
325pub struct AuthenticatedUser {
326 pub user_id: String,
328
329 pub scopes: Vec<String>,
331
332 pub expires_at: DateTime<Utc>,
334}
335
336impl fmt::Display for AuthenticatedUser {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 write!(
339 f,
340 "User({}, expires={})",
341 self.user_id,
342 self.expires_at.format("%Y-%m-%d %H:%M:%S UTC")
343 )
344 }
345}
346
347impl AuthenticatedUser {
348 #[must_use]
350 pub fn has_scope(&self, scope: &str) -> bool {
351 self.scopes.iter().any(|s| s == scope)
352 }
353
354 #[must_use]
356 pub fn is_expired(&self) -> bool {
357 self.expires_at <= Utc::now()
358 }
359
360 #[must_use]
362 pub fn ttl_secs(&self) -> i64 {
363 (self.expires_at - Utc::now()).num_seconds()
364 }
365}
366
367#[derive(Debug, Clone)]
371pub struct AuthRequest {
372 pub authorization_header: Option<String>,
374}
375
376impl AuthRequest {
377 #[must_use]
379 pub fn new(authorization_header: Option<String>) -> Self {
380 Self {
381 authorization_header,
382 }
383 }
384
385 pub fn extract_bearer_token(&self) -> Result<String> {
391 let header = self.authorization_header.as_ref().ok_or(SecurityError::AuthRequired)?;
392
393 if !header.starts_with("Bearer ") {
394 return Err(SecurityError::AuthRequired);
395 }
396
397 Ok(header[7..].to_string())
398 }
399}
400
401#[derive(Debug, Clone)]
406pub struct TokenClaims {
407 pub sub: Option<String>,
409
410 pub exp: Option<i64>,
412
413 pub scope: Option<String>,
415
416 pub aud: Option<Vec<String>>,
418
419 pub iss: Option<String>,
421}
422
423#[derive(Debug, Deserialize)]
428struct JwtClaims {
429 sub: Option<String>,
431
432 exp: Option<i64>,
434
435 #[serde(default)]
437 #[allow(dead_code)] iat: Option<i64>,
439
440 #[serde(default)]
442 #[allow(dead_code)] nbf: Option<i64>,
444
445 #[serde(default)]
447 scope: Option<String>,
448
449 #[serde(default)]
451 scp: Option<Vec<String>>,
452
453 #[serde(default)]
455 permissions: Option<Vec<String>>,
456
457 #[serde(default)]
459 #[allow(dead_code)] aud: Option<serde_json::Value>,
461
462 #[serde(default)]
464 #[allow(dead_code)] iss: Option<String>,
466}
467
468#[derive(Debug, Clone)]
473pub struct AuthMiddleware {
474 config: AuthConfig,
475}
476
477impl AuthMiddleware {
478 #[must_use]
480 pub fn from_config(config: AuthConfig) -> Self {
481 Self { config }
482 }
483
484 #[must_use]
486 pub fn permissive() -> Self {
487 Self::from_config(AuthConfig::permissive())
488 }
489
490 #[must_use]
492 pub fn standard() -> Self {
493 Self::from_config(AuthConfig::standard())
494 }
495
496 #[must_use]
498 pub fn strict() -> Self {
499 Self::from_config(AuthConfig::strict())
500 }
501
502 pub fn validate_request(&self, req: &AuthRequest) -> Result<AuthenticatedUser> {
514 let token = self.extract_token(req)?;
516
517 if let Some(ref signing_key) = self.config.signing_key {
519 self.validate_token_with_signature(&token, signing_key)
521 } else {
522 self.validate_token_structure_only(&token)
525 }
526 }
527
528 fn validate_token_with_signature(
532 &self,
533 token: &str,
534 signing_key: &SigningKey,
535 ) -> Result<AuthenticatedUser> {
536 let decoding_key = signing_key.to_decoding_key()?;
538
539 let mut validation = Validation::new(signing_key.algorithm());
541
542 if let Some(ref issuer) = self.config.issuer {
544 validation.set_issuer(&[issuer]);
545 }
546 if let Some(ref audience) = self.config.audience {
550 validation.set_audience(&[audience]);
551 } else {
552 validation.validate_aud = false;
553 }
554
555 validation.leeway = self.config.clock_skew_secs;
557
558 let token_data = decode::<JwtClaims>(token, &decoding_key, &validation).map_err(|e| {
560 match e.kind() {
561 jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
562 SecurityError::TokenExpired {
564 expired_at: Utc::now(), }
566 },
567 jsonwebtoken::errors::ErrorKind::InvalidSignature => SecurityError::InvalidToken,
568 jsonwebtoken::errors::ErrorKind::InvalidIssuer => SecurityError::InvalidToken,
569 jsonwebtoken::errors::ErrorKind::InvalidAudience => SecurityError::InvalidToken,
570 jsonwebtoken::errors::ErrorKind::InvalidAlgorithm => {
571 SecurityError::InvalidTokenAlgorithm {
572 algorithm: format!("{:?}", signing_key.algorithm()),
573 }
574 },
575 jsonwebtoken::errors::ErrorKind::MissingRequiredClaim(claim) => {
576 SecurityError::TokenMissingClaim {
577 claim: claim.clone(),
578 }
579 },
580 _ => SecurityError::InvalidToken,
581 }
582 })?;
583
584 let claims = token_data.claims;
585
586 let scopes = self.extract_scopes_from_jwt_claims(&claims);
588
589 let user_id = claims.sub.ok_or(SecurityError::TokenMissingClaim {
591 claim: "sub".to_string(),
592 })?;
593
594 let exp = claims.exp.ok_or(SecurityError::TokenMissingClaim {
596 claim: "exp".to_string(),
597 })?;
598
599 let expires_at =
600 DateTime::<Utc>::from_timestamp(exp, 0).ok_or(SecurityError::InvalidToken)?;
601
602 Ok(AuthenticatedUser {
603 user_id,
604 scopes,
605 expires_at,
606 })
607 }
608
609 fn extract_scopes_from_jwt_claims(&self, claims: &JwtClaims) -> Vec<String> {
616 if let Some(ref scope) = claims.scope {
618 return scope.split_whitespace().map(String::from).collect();
619 }
620
621 if let Some(ref scp) = claims.scp {
623 return scp.clone();
624 }
625
626 if let Some(ref permissions) = claims.permissions {
628 return permissions.clone();
629 }
630
631 Vec::new()
632 }
633
634 fn validate_token_structure_only(&self, token: &str) -> Result<AuthenticatedUser> {
639 self.validate_token_structure(token)?;
641
642 let claims = self.parse_claims(token)?;
644
645 let exp = claims.exp.ok_or(SecurityError::TokenMissingClaim {
647 claim: "exp".to_string(),
648 })?;
649
650 let expires_at =
652 DateTime::<Utc>::from_timestamp(exp, 0).ok_or(SecurityError::InvalidToken)?;
653
654 if expires_at <= Utc::now() {
655 return Err(SecurityError::TokenExpired {
656 expired_at: expires_at,
657 });
658 }
659
660 let user_id = claims.sub.ok_or(SecurityError::TokenMissingClaim {
662 claim: "sub".to_string(),
663 })?;
664
665 let scopes = claims
667 .scope
668 .as_ref()
669 .map(|s| s.split_whitespace().map(String::from).collect())
670 .unwrap_or_default();
671
672 Ok(AuthenticatedUser {
673 user_id,
674 scopes,
675 expires_at,
676 })
677 }
678
679 fn extract_token(&self, req: &AuthRequest) -> Result<String> {
681 if !self.config.required && req.authorization_header.is_none() {
683 return Err(SecurityError::AuthRequired); }
685
686 req.extract_bearer_token()
687 }
688
689 fn validate_token_structure(&self, token: &str) -> Result<()> {
694 let parts: Vec<&str> = token.split('.').collect();
696 if parts.len() != 3 {
697 return Err(SecurityError::InvalidToken);
698 }
699
700 if parts.iter().any(|p| p.is_empty()) {
702 return Err(SecurityError::InvalidToken);
703 }
704
705 Ok(())
706 }
707
708 fn parse_claims(&self, token: &str) -> Result<TokenClaims> {
713 let parts: Vec<&str> = token.split('.').collect();
715 if parts.len() != 3 {
716 return Err(SecurityError::InvalidToken);
717 }
718
719 let payload_part = parts[1];
723
724 if let Ok(decoded) = hex::decode(payload_part) {
727 if let Ok(json_str) = std::str::from_utf8(&decoded) {
728 if let Ok(json) = serde_json::from_str::<serde_json::Value>(json_str) {
729 return Ok(self.extract_claims_from_json(&json));
730 }
731 }
732 }
733
734 if let Ok(json) = serde_json::from_str::<serde_json::Value>(payload_part) {
736 return Ok(self.extract_claims_from_json(&json));
737 }
738
739 Err(SecurityError::InvalidToken)
740 }
741
742 fn extract_claims_from_json(&self, json: &serde_json::Value) -> TokenClaims {
744 let sub = json["sub"].as_str().map(String::from);
745 let exp = json["exp"].as_i64();
746 let scope = json["scope"].as_str().map(String::from);
747 let aud = json["aud"]
748 .as_array()
749 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect());
750 let iss = json["iss"].as_str().map(String::from);
751
752 TokenClaims {
753 sub,
754 exp,
755 scope,
756 aud,
757 iss,
758 }
759 }
760
761 #[must_use]
763 pub const fn config(&self) -> &AuthConfig {
764 &self.config
765 }
766}
767
768#[cfg(test)]
769mod tests {
770 use super::*;
771
772 fn create_test_token(sub: &str, exp_offset_secs: i64, scope: Option<&str>) -> String {
781 let now = chrono::Utc::now().timestamp();
782 let exp = now + exp_offset_secs;
783
784 let mut payload = serde_json::json!({
786 "sub": sub,
787 "exp": exp,
788 "iat": now,
789 "aud": ["test-audience"],
790 "iss": "test-issuer"
791 });
792
793 if let Some(s) = scope {
794 payload["scope"] = serde_json::json!(s);
795 }
796
797 let payload_json = payload.to_string();
799 let payload_hex = hex::encode(&payload_json);
800 let signature = "test_signature"; format!("header.{payload_hex}.{signature}")
804 }
805
806 #[test]
811 fn test_bearer_token_extracted_correctly() {
812 let token = "test_token_12345";
813 let req = AuthRequest::new(Some(format!("Bearer {token}")));
814
815 let result = req.extract_bearer_token();
816 assert!(result.is_ok());
817 assert_eq!(result.unwrap(), token);
818 }
819
820 #[test]
821 fn test_missing_authorization_header_rejected() {
822 let req = AuthRequest::new(None);
823
824 let result = req.extract_bearer_token();
825 assert!(matches!(result, Err(SecurityError::AuthRequired)));
826 }
827
828 #[test]
829 fn test_invalid_authorization_header_format_rejected() {
830 let req = AuthRequest::new(Some("Basic abc123".to_string()));
831
832 let result = req.extract_bearer_token();
833 assert!(matches!(result, Err(SecurityError::AuthRequired)));
834 }
835
836 #[test]
837 fn test_bearer_prefix_required() {
838 let req = AuthRequest::new(Some("abc123".to_string()));
839
840 let result = req.extract_bearer_token();
841 assert!(matches!(result, Err(SecurityError::AuthRequired)));
842 }
843
844 #[test]
849 fn test_valid_token_structure() {
850 let middleware = AuthMiddleware::permissive();
851 let token = create_test_token("user123", 3600, None);
852
853 let result = middleware.validate_token_structure(&token);
854 assert!(result.is_ok());
855 }
856
857 #[test]
858 fn test_token_with_wrong_part_count_rejected() {
859 let middleware = AuthMiddleware::permissive();
860 let token = "header.payload"; let result = middleware.validate_token_structure(token);
863 assert!(matches!(result, Err(SecurityError::InvalidToken)));
864 }
865
866 #[test]
867 fn test_token_with_empty_part_rejected() {
868 let middleware = AuthMiddleware::permissive();
869 let token = "header..signature"; let result = middleware.validate_token_structure(token);
872 assert!(matches!(result, Err(SecurityError::InvalidToken)));
873 }
874
875 #[test]
880 fn test_valid_token_not_expired() {
881 let middleware = AuthMiddleware::standard();
882 let token = create_test_token("user123", 3600, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
884
885 let result = middleware.validate_request(&req);
886 assert!(result.is_ok());
887 }
888
889 #[test]
890 fn test_expired_token_rejected() {
891 let middleware = AuthMiddleware::standard();
892 let token = create_test_token("user123", -3600, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
894
895 let result = middleware.validate_request(&req);
896 assert!(matches!(result, Err(SecurityError::TokenExpired { .. })));
897 }
898
899 #[test]
900 fn test_token_expiring_now_rejected() {
901 let middleware = AuthMiddleware::standard();
902 let token = create_test_token("user123", 0, None);
904 let req = AuthRequest::new(Some(format!("Bearer {token}")));
905
906 let result = middleware.validate_request(&req);
908 let _ = result;
910 }
911
912 #[test]
917 fn test_missing_sub_claim_rejected() {
918 let middleware = AuthMiddleware::standard();
919
920 let now = chrono::Utc::now().timestamp();
922 let payload = serde_json::json!({
923 "exp": now + 3600,
924 "iat": now
925 });
926
927 let payload_hex = hex::encode(payload.to_string());
928 let token = format!("header.{payload_hex}.signature");
929
930 let req = AuthRequest::new(Some(format!("Bearer {token}")));
931 let result = middleware.validate_request(&req);
932
933 assert!(matches!(
934 result,
935 Err(SecurityError::TokenMissingClaim { claim })
936 if claim == "sub"
937 ));
938 }
939
940 #[test]
941 fn test_missing_exp_claim_rejected() {
942 let middleware = AuthMiddleware::standard();
943
944 let payload = serde_json::json!({
946 "sub": "user123",
947 "iat": chrono::Utc::now().timestamp()
948 });
949
950 let payload_hex = hex::encode(payload.to_string());
951 let token = format!("header.{payload_hex}.signature");
952
953 let req = AuthRequest::new(Some(format!("Bearer {token}")));
954 let result = middleware.validate_request(&req);
955
956 assert!(matches!(
957 result,
958 Err(SecurityError::TokenMissingClaim { claim })
959 if claim == "exp"
960 ));
961 }
962
963 #[test]
968 fn test_user_id_extracted_from_token() {
969 let middleware = AuthMiddleware::standard();
970 let token = create_test_token("user_12345", 3600, None);
971 let req = AuthRequest::new(Some(format!("Bearer {token}")));
972
973 let result = middleware.validate_request(&req);
974 assert!(result.is_ok());
975
976 let user = result.unwrap();
977 assert_eq!(user.user_id, "user_12345");
978 }
979
980 #[test]
981 fn test_scopes_extracted_from_token() {
982 let middleware = AuthMiddleware::standard();
983 let token = create_test_token("user123", 3600, Some("read write admin"));
984 let req = AuthRequest::new(Some(format!("Bearer {token}")));
985
986 let result = middleware.validate_request(&req);
987 assert!(result.is_ok());
988
989 let user = result.unwrap();
990 assert_eq!(user.scopes, vec!["read", "write", "admin"]);
991 }
992
993 #[test]
994 fn test_empty_scopes_when_scope_claim_absent() {
995 let middleware = AuthMiddleware::standard();
996 let token = create_test_token("user123", 3600, None);
997 let req = AuthRequest::new(Some(format!("Bearer {token}")));
998
999 let result = middleware.validate_request(&req);
1000 assert!(result.is_ok());
1001
1002 let user = result.unwrap();
1003 assert!(user.scopes.is_empty());
1004 }
1005
1006 #[test]
1007 fn test_expires_at_extracted_correctly() {
1008 let middleware = AuthMiddleware::standard();
1009 let offset_secs = 7200; let token = create_test_token("user123", offset_secs, None);
1012 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1013
1014 let result = middleware.validate_request(&req);
1015 assert!(result.is_ok());
1016
1017 let user = result.unwrap();
1018 let now = Utc::now();
1019 let diff = (user.expires_at - now).num_seconds();
1020
1021 assert!((offset_secs - 5..=offset_secs + 5).contains(&diff));
1023 }
1024
1025 #[test]
1030 fn test_permissive_config() {
1031 let config = AuthConfig::permissive();
1032 assert!(!config.required);
1033 assert_eq!(config.token_expiry_secs, 3600);
1034 }
1035
1036 #[test]
1037 fn test_standard_config() {
1038 let config = AuthConfig::standard();
1039 assert!(config.required);
1040 assert_eq!(config.token_expiry_secs, 3600);
1041 }
1042
1043 #[test]
1044 fn test_strict_config() {
1045 let config = AuthConfig::strict();
1046 assert!(config.required);
1047 assert_eq!(config.token_expiry_secs, 1800);
1048 }
1049
1050 #[test]
1051 fn test_middleware_helpers() {
1052 let permissive = AuthMiddleware::permissive();
1053 assert!(!permissive.config().required);
1054
1055 let standard = AuthMiddleware::standard();
1056 assert!(standard.config().required);
1057
1058 let strict = AuthMiddleware::strict();
1059 assert!(strict.config().required);
1060 }
1061
1062 #[test]
1067 fn test_user_has_scope() {
1068 let user = AuthenticatedUser {
1069 user_id: "user123".to_string(),
1070 scopes: vec!["read".to_string(), "write".to_string()],
1071 expires_at: Utc::now() + chrono::Duration::hours(1),
1072 };
1073
1074 assert!(user.has_scope("read"));
1075 assert!(user.has_scope("write"));
1076 assert!(!user.has_scope("admin"));
1077 }
1078
1079 #[test]
1080 fn test_user_is_not_expired() {
1081 let user = AuthenticatedUser {
1082 user_id: "user123".to_string(),
1083 scopes: vec![],
1084 expires_at: Utc::now() + chrono::Duration::hours(1),
1085 };
1086
1087 assert!(!user.is_expired());
1088 }
1089
1090 #[test]
1091 fn test_user_is_expired() {
1092 let user = AuthenticatedUser {
1093 user_id: "user123".to_string(),
1094 scopes: vec![],
1095 expires_at: Utc::now() - chrono::Duration::hours(1),
1096 };
1097
1098 assert!(user.is_expired());
1099 }
1100
1101 #[test]
1102 fn test_user_ttl_calculation() {
1103 let now = Utc::now();
1104 let expires_at = now + chrono::Duration::hours(2);
1105 let user = AuthenticatedUser {
1106 user_id: "user123".to_string(),
1107 scopes: vec![],
1108 expires_at,
1109 };
1110
1111 let ttl = user.ttl_secs();
1112 assert!((7195..=7205).contains(&ttl));
1114 }
1115
1116 #[test]
1117 fn test_user_display() {
1118 let user = AuthenticatedUser {
1119 user_id: "user123".to_string(),
1120 scopes: vec![],
1121 expires_at: Utc::now() + chrono::Duration::hours(1),
1122 };
1123
1124 let display_str = user.to_string();
1125 assert!(display_str.contains("user123"));
1126 assert!(display_str.contains("UTC"));
1127 }
1128
1129 #[test]
1134 fn test_error_messages_clear_and_actionable() {
1135 let middleware = AuthMiddleware::standard();
1136
1137 let req = AuthRequest::new(None);
1139 let result = middleware.validate_request(&req);
1140 assert!(matches!(result, Err(SecurityError::AuthRequired)));
1141
1142 let req = AuthRequest::new(Some("Basic xyz".to_string()));
1144 let result = middleware.validate_request(&req);
1145 assert!(matches!(result, Err(SecurityError::AuthRequired)));
1146 }
1147
1148 #[test]
1153 fn test_auth_not_required_allows_missing_token() {
1154 let middleware = AuthMiddleware::permissive(); let req = AuthRequest::new(None);
1157
1158 let result = middleware.validate_request(&req);
1159 assert!(matches!(result, Err(SecurityError::AuthRequired)));
1161 }
1162
1163 #[test]
1164 fn test_whitespace_in_scopes_handled() {
1165 let middleware = AuthMiddleware::standard();
1166 let token = create_test_token("user123", 3600, Some(" read write admin "));
1167 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1168
1169 let result = middleware.validate_request(&req);
1170 assert!(result.is_ok());
1171
1172 let user = result.unwrap();
1173 assert_eq!(user.scopes.len(), 3);
1175 }
1176
1177 #[test]
1178 fn test_single_scope_parsed_correctly() {
1179 let middleware = AuthMiddleware::standard();
1180 let token = create_test_token("user123", 3600, Some("read"));
1181 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1182
1183 let result = middleware.validate_request(&req);
1184 assert!(result.is_ok());
1185
1186 let user = result.unwrap();
1187 assert_eq!(user.scopes, vec!["read"]);
1188 }
1189
1190 #[allow(clippy::items_after_statements)] fn create_signed_hs256_token(
1197 sub: &str,
1198 exp_offset_secs: i64,
1199 scope: Option<&str>,
1200 secret: &str,
1201 ) -> String {
1202 use jsonwebtoken::{EncodingKey, Header, encode};
1203
1204 let now = chrono::Utc::now().timestamp();
1205 let exp = now + exp_offset_secs;
1206
1207 #[derive(serde::Serialize)]
1208 struct Claims {
1209 sub: String,
1210 exp: i64,
1211 iat: i64,
1212 #[serde(skip_serializing_if = "Option::is_none")]
1213 scope: Option<String>,
1214 }
1215
1216 let claims = Claims {
1217 sub: sub.to_string(),
1218 exp,
1219 iat: now,
1220 scope: scope.map(String::from),
1221 };
1222
1223 encode(
1224 &Header::default(), &claims,
1226 &EncodingKey::from_secret(secret.as_bytes()),
1227 )
1228 .expect("Failed to create test token")
1229 }
1230
1231 #[test]
1232 fn test_hs256_signature_verification_valid_token() {
1233 let secret = "super-secret-key-for-testing-only";
1234 let config = AuthConfig::with_hs256(secret);
1235 let middleware = AuthMiddleware::from_config(config);
1236
1237 let token = create_signed_hs256_token("user123", 3600, Some("read write"), secret);
1238 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1239
1240 let result = middleware.validate_request(&req);
1241 assert!(result.is_ok(), "Expected valid token, got: {:?}", result);
1242
1243 let user = result.unwrap();
1244 assert_eq!(user.user_id, "user123");
1245 assert_eq!(user.scopes, vec!["read", "write"]);
1246 }
1247
1248 #[test]
1249 fn test_hs256_signature_verification_wrong_secret_rejected() {
1250 let signing_secret = "correct-secret";
1251 let wrong_secret = "wrong-secret";
1252
1253 let config = AuthConfig::with_hs256(signing_secret);
1254 let middleware = AuthMiddleware::from_config(config);
1255
1256 let token = create_signed_hs256_token("user123", 3600, None, wrong_secret);
1258 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1259
1260 let result = middleware.validate_request(&req);
1261 assert!(
1262 matches!(result, Err(SecurityError::InvalidToken)),
1263 "Expected InvalidToken for wrong signature, got: {:?}",
1264 result
1265 );
1266 }
1267
1268 #[test]
1269 fn test_hs256_expired_token_rejected() {
1270 let secret = "test-secret";
1271 let config = AuthConfig::with_hs256(secret);
1272 let middleware = AuthMiddleware::from_config(config);
1273
1274 let token = create_signed_hs256_token("user123", -3600, None, secret);
1276 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1277
1278 let result = middleware.validate_request(&req);
1279 assert!(
1280 matches!(result, Err(SecurityError::TokenExpired { .. })),
1281 "Expected TokenExpired, got: {:?}",
1282 result
1283 );
1284 }
1285
1286 #[test]
1287 #[allow(clippy::items_after_statements)] fn test_hs256_with_issuer_validation() {
1289 use jsonwebtoken::{EncodingKey, Header, encode};
1290
1291 #[derive(serde::Serialize)]
1292 struct ClaimsWithIss {
1293 sub: String,
1294 exp: i64,
1295 iss: String,
1296 }
1297
1298 let secret = "test-secret";
1299 let config = AuthConfig::with_hs256(secret).with_issuer("https://auth.example.com");
1300 let middleware = AuthMiddleware::from_config(config);
1301
1302 let now = chrono::Utc::now().timestamp();
1304 let claims = ClaimsWithIss {
1305 sub: "user123".to_string(),
1306 exp: now + 3600,
1307 iss: "https://auth.example.com".to_string(),
1308 };
1309
1310 let token =
1311 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
1312 .unwrap();
1313
1314 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1315 let result = middleware.validate_request(&req);
1316 assert!(result.is_ok(), "Expected valid token with issuer, got: {:?}", result);
1317 }
1318
1319 #[test]
1320 #[allow(clippy::items_after_statements)] fn test_hs256_with_wrong_issuer_rejected() {
1322 use jsonwebtoken::{EncodingKey, Header, encode};
1323
1324 #[derive(serde::Serialize)]
1325 struct ClaimsWithIss {
1326 sub: String,
1327 exp: i64,
1328 iss: String,
1329 }
1330
1331 let secret = "test-secret";
1332 let config = AuthConfig::with_hs256(secret).with_issuer("https://auth.example.com");
1333 let middleware = AuthMiddleware::from_config(config);
1334
1335 let now = chrono::Utc::now().timestamp();
1337 let claims = ClaimsWithIss {
1338 sub: "user123".to_string(),
1339 exp: now + 3600,
1340 iss: "https://wrong-issuer.com".to_string(),
1341 };
1342
1343 let token =
1344 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
1345 .unwrap();
1346
1347 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1348 let result = middleware.validate_request(&req);
1349 assert!(
1350 matches!(result, Err(SecurityError::InvalidToken)),
1351 "Expected InvalidToken for wrong issuer, got: {:?}",
1352 result
1353 );
1354 }
1355
1356 #[test]
1357 #[allow(clippy::items_after_statements)] fn test_hs256_with_audience_validation() {
1359 use jsonwebtoken::{EncodingKey, Header, encode};
1360
1361 #[derive(serde::Serialize)]
1362 struct ClaimsWithAud {
1363 sub: String,
1364 exp: i64,
1365 aud: String,
1366 }
1367
1368 let secret = "test-secret";
1369 let config = AuthConfig::with_hs256(secret).with_audience("my-api");
1370 let middleware = AuthMiddleware::from_config(config);
1371
1372 let now = chrono::Utc::now().timestamp();
1374 let claims = ClaimsWithAud {
1375 sub: "user123".to_string(),
1376 exp: now + 3600,
1377 aud: "my-api".to_string(),
1378 };
1379
1380 let token =
1381 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
1382 .unwrap();
1383
1384 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1385 let result = middleware.validate_request(&req);
1386 assert!(result.is_ok(), "Expected valid token with audience, got: {:?}", result);
1387 }
1388
1389 #[test]
1390 fn test_signing_key_algorithm_detection() {
1391 use jsonwebtoken::Algorithm;
1392
1393 let hs256 = SigningKey::hs256("secret");
1394 assert!(matches!(hs256.algorithm(), Algorithm::HS256));
1395
1396 let hs384 = SigningKey::Hs384(Zeroizing::new(b"secret".to_vec()));
1397 assert!(matches!(hs384.algorithm(), Algorithm::HS384));
1398
1399 let hs512 = SigningKey::Hs512(Zeroizing::new(b"secret".to_vec()));
1400 assert!(matches!(hs512.algorithm(), Algorithm::HS512));
1401
1402 let rs256_pem = SigningKey::rs256_pem("fake-pem");
1403 assert!(matches!(rs256_pem.algorithm(), Algorithm::RS256));
1404
1405 let rs256_comp = SigningKey::rs256_components("n", "e");
1406 assert!(matches!(rs256_comp.algorithm(), Algorithm::RS256));
1407 }
1408
1409 #[test]
1410 fn test_config_has_signing_key() {
1411 let config_without = AuthConfig::standard();
1412 assert!(!config_without.has_signing_key());
1413
1414 let config_with = AuthConfig::with_hs256("secret");
1415 assert!(config_with.has_signing_key());
1416 }
1417
1418 #[test]
1419 fn test_config_builder_pattern() {
1420 let config = AuthConfig::with_hs256("secret")
1421 .with_issuer("https://auth.example.com")
1422 .with_audience("my-api");
1423
1424 assert!(config.has_signing_key());
1425 assert_eq!(config.issuer, Some("https://auth.example.com".to_string()));
1426 assert_eq!(config.audience, Some("my-api".to_string()));
1427 }
1428
1429 #[test]
1430 fn test_malformed_token_rejected_with_signature_verification() {
1431 let config = AuthConfig::with_hs256("secret");
1432 let middleware = AuthMiddleware::from_config(config);
1433
1434 let req = AuthRequest::new(Some("Bearer not-a-jwt".to_string()));
1436 let result = middleware.validate_request(&req);
1437 assert!(
1438 matches!(result, Err(SecurityError::InvalidToken)),
1439 "Expected InvalidToken for malformed JWT, got: {:?}",
1440 result
1441 );
1442 }
1443
1444 #[test]
1445 fn test_tampered_payload_rejected() {
1446 let secret = "test-secret";
1447 let config = AuthConfig::with_hs256(secret);
1448 let middleware = AuthMiddleware::from_config(config);
1449
1450 let token = create_signed_hs256_token("user123", 3600, None, secret);
1452
1453 let parts: Vec<&str> = token.split('.').collect();
1455 let tampered_token = format!("{}.dGFtcGVyZWQ.{}", parts[0], parts[2]);
1456
1457 let req = AuthRequest::new(Some(format!("Bearer {tampered_token}")));
1458 let result = middleware.validate_request(&req);
1459 assert!(
1460 matches!(result, Err(SecurityError::InvalidToken)),
1461 "Expected InvalidToken for tampered payload, got: {:?}",
1462 result
1463 );
1464 }
1465
1466 #[test]
1467 fn test_clock_skew_tolerance() {
1468 let secret = "test-secret";
1469 let mut config = AuthConfig::with_hs256(secret);
1470 config.clock_skew_secs = 120; let middleware = AuthMiddleware::from_config(config);
1472
1473 let token = create_signed_hs256_token("user123", -30, None, secret);
1475 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1476
1477 let result = middleware.validate_request(&req);
1478 assert!(result.is_ok(), "Expected valid token within clock skew, got: {:?}", result);
1480 }
1481}