1use chrono::{DateTime, Utc};
4use jsonwebtoken::{Validation, decode};
5
6use super::{
7 config::AuthConfig,
8 signing_key::SigningKey,
9 types::{AuthRequest, AuthenticatedUser, JwtClaims, TokenClaims},
10};
11use crate::security::errors::{Result, SecurityError};
12
13#[derive(Debug, Clone)]
18pub struct AuthMiddleware {
19 config: AuthConfig,
20}
21
22impl AuthMiddleware {
23 #[must_use]
28 pub fn from_config(config: AuthConfig) -> Self {
29 if config.required && config.signing_key.is_none() {
30 tracing::warn!(
31 "AuthMiddleware: required=true but no signing_key configured — \
32 JWT signatures will NOT be verified"
33 );
34 }
35 Self { config }
36 }
37
38 #[must_use]
40 pub fn permissive() -> Self {
41 Self::from_config(AuthConfig::permissive())
42 }
43
44 #[must_use]
46 pub fn standard() -> Self {
47 Self::from_config(AuthConfig::standard())
48 }
49
50 #[must_use]
52 pub fn strict() -> Self {
53 Self::from_config(AuthConfig::strict())
54 }
55
56 pub fn validate_request(&self, req: &AuthRequest) -> Result<AuthenticatedUser> {
73 let token = self.extract_token(req)?;
75
76 if let Some(ref signing_key) = self.config.signing_key {
78 self.validate_token_with_signature(&token, signing_key)
80 } else {
81 self.validate_token_structure_only(&token)
84 }
85 }
86
87 fn validate_token_with_signature(
91 &self,
92 token: &str,
93 signing_key: &SigningKey,
94 ) -> Result<AuthenticatedUser> {
95 let decoding_key = signing_key.to_decoding_key()?;
97
98 let mut validation = Validation::new(signing_key.algorithm());
100
101 if let Some(ref issuer) = self.config.issuer {
103 validation.set_issuer(&[issuer]);
104 }
105 if let Some(ref audience) = self.config.audience {
114 validation.set_audience(&[audience]);
115 }
116 validation.leeway = self.config.clock_skew_secs;
121
122 let token_data = decode::<JwtClaims>(token, &decoding_key, &validation).map_err(|e| {
124 match e.kind() {
125 jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
126 SecurityError::TokenExpired {
128 expired_at: Utc::now(), }
130 },
131 jsonwebtoken::errors::ErrorKind::InvalidSignature => {
132 SecurityError::JwtSignatureInvalid
133 },
134 jsonwebtoken::errors::ErrorKind::InvalidIssuer => {
135 SecurityError::JwtIssuerMismatch {
136 expected: self
137 .config
138 .issuer
139 .clone()
140 .unwrap_or_else(|| "(not configured)".to_string()),
141 }
142 },
143 jsonwebtoken::errors::ErrorKind::InvalidAudience => {
144 SecurityError::JwtAudienceMismatch {
145 expected: self
146 .config
147 .audience
148 .clone()
149 .unwrap_or_else(|| "(not configured)".to_string()),
150 }
151 },
152 jsonwebtoken::errors::ErrorKind::InvalidAlgorithm => {
153 SecurityError::InvalidTokenAlgorithm {
154 algorithm: format!("{:?}", signing_key.algorithm()),
155 }
156 },
157 jsonwebtoken::errors::ErrorKind::MissingRequiredClaim(claim) => {
158 SecurityError::TokenMissingClaim {
159 claim: claim.clone(),
160 }
161 },
162 _ => SecurityError::InvalidToken,
163 }
164 })?;
165
166 let claims = token_data.claims;
167
168 let scopes = self.extract_scopes_from_jwt_claims(&claims);
170
171 let user_id = claims.sub.ok_or(SecurityError::TokenMissingClaim {
173 claim: "sub".to_string(),
174 })?;
175
176 let exp = claims.exp.ok_or(SecurityError::TokenMissingClaim {
178 claim: "exp".to_string(),
179 })?;
180
181 let expires_at =
182 DateTime::<Utc>::from_timestamp(exp, 0).ok_or(SecurityError::InvalidToken)?;
183
184 Ok(AuthenticatedUser {
185 user_id,
186 scopes,
187 expires_at,
188 })
189 }
190
191 fn extract_scopes_from_jwt_claims(&self, claims: &JwtClaims) -> Vec<String> {
198 if let Some(ref scope) = claims.scope {
200 return scope.split_whitespace().map(String::from).collect();
201 }
202
203 if let Some(ref scp) = claims.scp {
205 return scp.clone();
206 }
207
208 if let Some(ref permissions) = claims.permissions {
210 return permissions.clone();
211 }
212
213 Vec::new()
214 }
215
216 fn validate_token_structure_only(&self, token: &str) -> Result<AuthenticatedUser> {
221 self.validate_token_structure(token)?;
223
224 let claims = self.parse_claims(token)?;
226
227 let exp = claims.exp.ok_or(SecurityError::TokenMissingClaim {
229 claim: "exp".to_string(),
230 })?;
231
232 let expires_at =
234 DateTime::<Utc>::from_timestamp(exp, 0).ok_or(SecurityError::InvalidToken)?;
235
236 if expires_at <= Utc::now() {
237 return Err(SecurityError::TokenExpired {
238 expired_at: expires_at,
239 });
240 }
241
242 let user_id = claims.sub.ok_or(SecurityError::TokenMissingClaim {
244 claim: "sub".to_string(),
245 })?;
246
247 let scopes = claims
249 .scope
250 .as_ref()
251 .map(|s| s.split_whitespace().map(String::from).collect())
252 .unwrap_or_default();
253
254 Ok(AuthenticatedUser {
255 user_id,
256 scopes,
257 expires_at,
258 })
259 }
260
261 fn extract_token(&self, req: &AuthRequest) -> Result<String> {
263 if !self.config.required && req.authorization_header.is_none() {
265 return Err(SecurityError::AuthRequired); }
267
268 req.extract_bearer_token()
269 }
270
271 fn validate_token_structure(&self, token: &str) -> Result<()> {
276 let parts: Vec<&str> = token.split('.').collect();
278 if parts.len() != 3 {
279 return Err(SecurityError::InvalidToken);
280 }
281
282 if parts.iter().any(|p| p.is_empty()) {
284 return Err(SecurityError::InvalidToken);
285 }
286
287 Ok(())
288 }
289
290 fn parse_claims(&self, token: &str) -> Result<TokenClaims> {
295 let parts: Vec<&str> = token.split('.').collect();
297 if parts.len() != 3 {
298 return Err(SecurityError::InvalidToken);
299 }
300
301 let payload_part = parts[1];
305
306 if let Ok(decoded) = hex::decode(payload_part) {
309 if let Ok(json_str) = std::str::from_utf8(&decoded) {
310 if let Ok(json) = serde_json::from_str::<serde_json::Value>(json_str) {
311 return Ok(self.extract_claims_from_json(&json));
312 }
313 }
314 }
315
316 if let Ok(json) = serde_json::from_str::<serde_json::Value>(payload_part) {
318 return Ok(self.extract_claims_from_json(&json));
319 }
320
321 Err(SecurityError::InvalidToken)
322 }
323
324 fn extract_claims_from_json(&self, json: &serde_json::Value) -> TokenClaims {
326 let sub = json["sub"].as_str().map(String::from);
327 let exp = json["exp"].as_i64();
328 let scope = json["scope"].as_str().map(String::from);
329 let aud = json["aud"]
330 .as_array()
331 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect());
332 let iss = json["iss"].as_str().map(String::from);
333
334 TokenClaims {
335 sub,
336 exp,
337 scope,
338 aud,
339 iss,
340 }
341 }
342
343 #[must_use]
345 pub const fn config(&self) -> &AuthConfig {
346 &self.config
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 #![allow(clippy::unwrap_used)] use chrono::Utc;
355 use jsonwebtoken::Algorithm;
356 use zeroize::Zeroizing;
357
358 use super::{
359 super::{
360 config::AuthConfig,
361 signing_key::SigningKey,
362 types::{AuthRequest, AuthenticatedUser},
363 },
364 *,
365 };
366 use crate::security::errors::SecurityError;
367
368 fn create_test_token(sub: &str, exp_offset_secs: i64, scope: Option<&str>) -> String {
377 let now = chrono::Utc::now().timestamp();
378 let exp = now + exp_offset_secs;
379
380 let mut payload = serde_json::json!({
382 "sub": sub,
383 "exp": exp,
384 "iat": now,
385 "aud": ["test-audience"],
386 "iss": "test-issuer"
387 });
388
389 if let Some(s) = scope {
390 payload["scope"] = serde_json::json!(s);
391 }
392
393 let payload_json = payload.to_string();
395 let payload_hex = hex::encode(&payload_json);
396 let signature = "test_signature"; format!("header.{payload_hex}.{signature}")
400 }
401
402 #[test]
407 fn test_bearer_token_extracted_correctly() {
408 let token = "test_token_12345";
409 let req = AuthRequest::new(Some(format!("Bearer {token}")));
410
411 let extracted = req
412 .extract_bearer_token()
413 .unwrap_or_else(|e| panic!("expected bearer token extraction to succeed: {e}"));
414 assert_eq!(extracted, token);
415 }
416
417 #[test]
418 fn test_missing_authorization_header_rejected() {
419 let req = AuthRequest::new(None);
420
421 let result = req.extract_bearer_token();
422 assert!(matches!(result, Err(SecurityError::AuthRequired)));
423 }
424
425 #[test]
426 fn test_invalid_authorization_header_format_rejected() {
427 let req = AuthRequest::new(Some("Basic abc123".to_string()));
428
429 let result = req.extract_bearer_token();
430 assert!(matches!(result, Err(SecurityError::AuthRequired)));
431 }
432
433 #[test]
434 fn test_bearer_prefix_required() {
435 let req = AuthRequest::new(Some("abc123".to_string()));
436
437 let result = req.extract_bearer_token();
438 assert!(matches!(result, Err(SecurityError::AuthRequired)));
439 }
440
441 #[test]
446 fn test_valid_token_structure() {
447 let middleware = AuthMiddleware::permissive();
448 let token = create_test_token("user123", 3600, None);
449
450 let result = middleware.validate_token_structure(&token);
451 result.unwrap_or_else(|e| panic!("expected valid token structure: {e}"));
452 }
453
454 #[test]
455 fn test_token_with_wrong_part_count_rejected() {
456 let middleware = AuthMiddleware::permissive();
457 let token = "header.payload"; let result = middleware.validate_token_structure(token);
460 assert!(matches!(result, Err(SecurityError::InvalidToken)));
461 }
462
463 #[test]
464 fn test_token_with_empty_part_rejected() {
465 let middleware = AuthMiddleware::permissive();
466 let token = "header..signature"; let result = middleware.validate_token_structure(token);
469 assert!(matches!(result, Err(SecurityError::InvalidToken)));
470 }
471
472 #[test]
477 fn test_valid_token_not_expired() {
478 let middleware = AuthMiddleware::standard();
479 let token = create_test_token("user123", 3600, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
481
482 let result = middleware.validate_request(&req);
483 result.unwrap_or_else(|e| panic!("expected valid non-expired token to pass: {e}"));
484 }
485
486 #[test]
487 fn test_expired_token_rejected() {
488 let middleware = AuthMiddleware::standard();
489 let token = create_test_token("user123", -3600, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
491
492 let result = middleware.validate_request(&req);
493 assert!(matches!(result, Err(SecurityError::TokenExpired { .. })));
494 }
495
496 #[test]
505 fn test_token_expired_one_second_ago_is_rejected() {
506 let middleware = AuthMiddleware::standard();
507 let token = create_test_token("user123", -1, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
509
510 assert!(
511 matches!(middleware.validate_request(&req), Err(SecurityError::TokenExpired { .. })),
512 "token expired 1s ago must be rejected"
513 );
514 }
515
516 #[test]
521 fn test_token_expiring_soon_is_accepted() {
522 let middleware = AuthMiddleware::standard();
523 let token = create_test_token("user123", 60, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
525
526 assert!(
527 middleware.validate_request(&req).is_ok(),
528 "token expiring in 60s must be accepted"
529 );
530 }
531
532 #[test]
537 fn test_missing_sub_claim_rejected() {
538 let middleware = AuthMiddleware::standard();
539
540 let now = chrono::Utc::now().timestamp();
542 let payload = serde_json::json!({
543 "exp": now + 3600,
544 "iat": now
545 });
546
547 let payload_hex = hex::encode(payload.to_string());
548 let token = format!("header.{payload_hex}.signature");
549
550 let req = AuthRequest::new(Some(format!("Bearer {token}")));
551 let result = middleware.validate_request(&req);
552
553 assert!(matches!(
554 result,
555 Err(SecurityError::TokenMissingClaim { claim })
556 if claim == "sub"
557 ));
558 }
559
560 #[test]
561 fn test_missing_exp_claim_rejected() {
562 let middleware = AuthMiddleware::standard();
563
564 let payload = serde_json::json!({
566 "sub": "user123",
567 "iat": chrono::Utc::now().timestamp()
568 });
569
570 let payload_hex = hex::encode(payload.to_string());
571 let token = format!("header.{payload_hex}.signature");
572
573 let req = AuthRequest::new(Some(format!("Bearer {token}")));
574 let result = middleware.validate_request(&req);
575
576 assert!(matches!(
577 result,
578 Err(SecurityError::TokenMissingClaim { claim })
579 if claim == "exp"
580 ));
581 }
582
583 #[test]
588 fn test_user_id_extracted_from_token() {
589 let middleware = AuthMiddleware::standard();
590 let token = create_test_token("user_12345", 3600, None);
591 let req = AuthRequest::new(Some(format!("Bearer {token}")));
592
593 let user = middleware
594 .validate_request(&req)
595 .unwrap_or_else(|e| panic!("expected user_id extraction to succeed: {e}"));
596 assert_eq!(user.user_id, "user_12345");
597 }
598
599 #[test]
600 fn test_scopes_extracted_from_token() {
601 let middleware = AuthMiddleware::standard();
602 let token = create_test_token("user123", 3600, Some("read write admin"));
603 let req = AuthRequest::new(Some(format!("Bearer {token}")));
604
605 let user = middleware
606 .validate_request(&req)
607 .unwrap_or_else(|e| panic!("expected scope extraction to succeed: {e}"));
608 assert_eq!(user.scopes, vec!["read", "write", "admin"]);
609 }
610
611 #[test]
612 fn test_empty_scopes_when_scope_claim_absent() {
613 let middleware = AuthMiddleware::standard();
614 let token = create_test_token("user123", 3600, None);
615 let req = AuthRequest::new(Some(format!("Bearer {token}")));
616
617 let user = middleware
618 .validate_request(&req)
619 .unwrap_or_else(|e| panic!("expected token without scopes to be valid: {e}"));
620 assert!(user.scopes.is_empty(), "expected empty scopes, got: {:?}", user.scopes);
621 }
622
623 #[test]
624 fn test_expires_at_extracted_correctly() {
625 let middleware = AuthMiddleware::standard();
626 let offset_secs = 7200; let token = create_test_token("user123", offset_secs, None);
629 let req = AuthRequest::new(Some(format!("Bearer {token}")));
630
631 let user = middleware
632 .validate_request(&req)
633 .unwrap_or_else(|e| panic!("expected expiry extraction to succeed: {e}"));
634 let now = Utc::now();
635 let diff = (user.expires_at - now).num_seconds();
636
637 assert!((offset_secs - 5..=offset_secs + 5).contains(&diff));
639 }
640
641 #[test]
646 fn test_permissive_config() {
647 let config = AuthConfig::permissive();
648 assert!(!config.required);
649 assert_eq!(config.token_expiry_secs, 3600);
650 }
651
652 #[test]
653 fn test_standard_config() {
654 let config = AuthConfig::standard();
655 assert!(config.required);
656 assert_eq!(config.token_expiry_secs, 3600);
657 }
658
659 #[test]
660 fn test_strict_config() {
661 let config = AuthConfig::strict();
662 assert!(config.required);
663 assert_eq!(config.token_expiry_secs, 1800);
664 }
665
666 #[test]
667 fn test_middleware_helpers() {
668 let permissive = AuthMiddleware::permissive();
669 assert!(!permissive.config().required);
670
671 let standard = AuthMiddleware::standard();
672 assert!(standard.config().required);
673
674 let strict = AuthMiddleware::strict();
675 assert!(strict.config().required);
676 }
677
678 #[test]
683 fn test_user_has_scope() {
684 let user = AuthenticatedUser {
685 user_id: "user123".to_string(),
686 scopes: vec!["read".to_string(), "write".to_string()],
687 expires_at: Utc::now() + chrono::Duration::hours(1),
688 };
689
690 assert!(user.has_scope("read"));
691 assert!(user.has_scope("write"));
692 assert!(!user.has_scope("admin"));
693 }
694
695 #[test]
696 fn test_user_is_not_expired() {
697 let user = AuthenticatedUser {
698 user_id: "user123".to_string(),
699 scopes: vec![],
700 expires_at: Utc::now() + chrono::Duration::hours(1),
701 };
702
703 assert!(!user.is_expired());
704 }
705
706 #[test]
707 fn test_user_is_expired() {
708 let user = AuthenticatedUser {
709 user_id: "user123".to_string(),
710 scopes: vec![],
711 expires_at: Utc::now() - chrono::Duration::hours(1),
712 };
713
714 assert!(user.is_expired());
715 }
716
717 #[test]
718 fn test_user_ttl_calculation() {
719 let now = Utc::now();
720 let expires_at = now + chrono::Duration::hours(2);
721 let user = AuthenticatedUser {
722 user_id: "user123".to_string(),
723 scopes: vec![],
724 expires_at,
725 };
726
727 let ttl = user.ttl_secs();
728 assert!((7195..=7205).contains(&ttl));
730 }
731
732 #[test]
733 fn test_user_display() {
734 let user = AuthenticatedUser {
735 user_id: "user123".to_string(),
736 scopes: vec![],
737 expires_at: Utc::now() + chrono::Duration::hours(1),
738 };
739
740 let display_str = user.to_string();
741 assert!(display_str.contains("user123"));
742 assert!(display_str.contains("UTC"));
743 }
744
745 #[test]
750 fn test_error_messages_clear_and_actionable() {
751 let middleware = AuthMiddleware::standard();
752
753 let req = AuthRequest::new(None);
755 let result = middleware.validate_request(&req);
756 assert!(matches!(result, Err(SecurityError::AuthRequired)));
757
758 let req = AuthRequest::new(Some("Basic xyz".to_string()));
760 let result = middleware.validate_request(&req);
761 assert!(matches!(result, Err(SecurityError::AuthRequired)));
762 }
763
764 #[test]
769 fn test_auth_not_required_allows_missing_token() {
770 let middleware = AuthMiddleware::permissive(); let req = AuthRequest::new(None);
773
774 let result = middleware.validate_request(&req);
775 assert!(matches!(result, Err(SecurityError::AuthRequired)));
777 }
778
779 #[test]
780 fn test_whitespace_in_scopes_handled() {
781 let middleware = AuthMiddleware::standard();
782 let token = create_test_token("user123", 3600, Some(" read write admin "));
783 let req = AuthRequest::new(Some(format!("Bearer {token}")));
784
785 let user = middleware
786 .validate_request(&req)
787 .unwrap_or_else(|e| panic!("expected whitespace-heavy scopes to parse: {e}"));
788 assert_eq!(user.scopes.len(), 3);
790 }
791
792 #[test]
793 fn test_single_scope_parsed_correctly() {
794 let middleware = AuthMiddleware::standard();
795 let token = create_test_token("user123", 3600, Some("read"));
796 let req = AuthRequest::new(Some(format!("Bearer {token}")));
797
798 let user = middleware
799 .validate_request(&req)
800 .unwrap_or_else(|e| panic!("expected single scope to parse: {e}"));
801 assert_eq!(user.scopes, vec!["read"]);
802 }
803
804 #[allow(clippy::items_after_statements)] fn create_signed_hs256_token(
811 sub: &str,
812 exp_offset_secs: i64,
813 scope: Option<&str>,
814 secret: &str,
815 ) -> String {
816 use jsonwebtoken::{EncodingKey, Header, encode};
817
818 let now = chrono::Utc::now().timestamp();
819 let exp = now + exp_offset_secs;
820
821 #[derive(serde::Serialize)]
822 struct Claims {
823 sub: String,
824 exp: i64,
825 iat: i64,
826 #[serde(skip_serializing_if = "Option::is_none")]
827 scope: Option<String>,
828 }
829
830 let claims = Claims {
831 sub: sub.to_string(),
832 exp,
833 iat: now,
834 scope: scope.map(String::from),
835 };
836
837 encode(
838 &Header::default(), &claims,
840 &EncodingKey::from_secret(secret.as_bytes()),
841 )
842 .expect("Failed to create test token")
843 }
844
845 #[test]
846 fn test_hs256_signature_verification_valid_token() {
847 let secret = "super-secret-key-for-testing-only";
848 let config = AuthConfig::with_hs256(secret);
849 let middleware = AuthMiddleware::from_config(config);
850
851 let token = create_signed_hs256_token("user123", 3600, Some("read write"), secret);
852 let req = AuthRequest::new(Some(format!("Bearer {token}")));
853
854 let result = middleware.validate_request(&req);
855 assert!(result.is_ok(), "Expected valid token, got: {:?}", result);
856
857 let user = result.unwrap();
858 assert_eq!(user.user_id, "user123");
859 assert_eq!(user.scopes, vec!["read", "write"]);
860 }
861
862 #[test]
863 fn test_hs256_signature_verification_wrong_secret_rejected() {
864 let signing_secret = "correct-secret";
865 let wrong_secret = "wrong-secret";
866
867 let config = AuthConfig::with_hs256(signing_secret);
868 let middleware = AuthMiddleware::from_config(config);
869
870 let token = create_signed_hs256_token("user123", 3600, None, wrong_secret);
872 let req = AuthRequest::new(Some(format!("Bearer {token}")));
873
874 let result = middleware.validate_request(&req);
875 assert!(
876 matches!(result, Err(SecurityError::JwtSignatureInvalid)),
877 "Expected JwtSignatureInvalid for wrong signature, got: {:?}",
878 result
879 );
880 }
881
882 #[test]
883 fn test_hs256_expired_token_rejected() {
884 let secret = "test-secret";
885 let config = AuthConfig::with_hs256(secret);
886 let middleware = AuthMiddleware::from_config(config);
887
888 let token = create_signed_hs256_token("user123", -3600, None, secret);
890 let req = AuthRequest::new(Some(format!("Bearer {token}")));
891
892 let result = middleware.validate_request(&req);
893 assert!(
894 matches!(result, Err(SecurityError::TokenExpired { .. })),
895 "Expected TokenExpired, got: {:?}",
896 result
897 );
898 }
899
900 #[test]
901 #[allow(clippy::items_after_statements)] fn test_hs256_with_issuer_validation() {
903 use jsonwebtoken::{EncodingKey, Header, encode};
904
905 #[derive(serde::Serialize)]
906 struct ClaimsWithIss {
907 sub: String,
908 exp: i64,
909 iss: String,
910 }
911
912 let secret = "test-secret";
913 let config = AuthConfig::with_hs256(secret).with_issuer("https://auth.example.com");
914 let middleware = AuthMiddleware::from_config(config);
915
916 let now = chrono::Utc::now().timestamp();
918 let claims = ClaimsWithIss {
919 sub: "user123".to_string(),
920 exp: now + 3600,
921 iss: "https://auth.example.com".to_string(),
922 };
923
924 let token =
925 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
926 .unwrap();
927
928 let req = AuthRequest::new(Some(format!("Bearer {token}")));
929 let result = middleware.validate_request(&req);
930 assert!(result.is_ok(), "Expected valid token with issuer, got: {:?}", result);
931 }
932
933 #[test]
934 #[allow(clippy::items_after_statements)] fn test_hs256_with_wrong_issuer_rejected() {
936 use jsonwebtoken::{EncodingKey, Header, encode};
937
938 #[derive(serde::Serialize)]
939 struct ClaimsWithIss {
940 sub: String,
941 exp: i64,
942 iss: String,
943 }
944
945 let secret = "test-secret";
946 let config = AuthConfig::with_hs256(secret).with_issuer("https://auth.example.com");
947 let middleware = AuthMiddleware::from_config(config);
948
949 let now = chrono::Utc::now().timestamp();
951 let claims = ClaimsWithIss {
952 sub: "user123".to_string(),
953 exp: now + 3600,
954 iss: "https://wrong-issuer.com".to_string(),
955 };
956
957 let token =
958 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
959 .unwrap();
960
961 let req = AuthRequest::new(Some(format!("Bearer {token}")));
962 let result = middleware.validate_request(&req);
963 assert!(
964 matches!(result, Err(SecurityError::JwtIssuerMismatch { .. })),
965 "Expected JwtIssuerMismatch for wrong issuer, got: {:?}",
966 result
967 );
968 }
969
970 #[test]
971 #[allow(clippy::items_after_statements)] fn test_jwt_issuer_mismatch_error_contains_expected_issuer_and_word_issuer() {
973 use jsonwebtoken::{EncodingKey, Header, encode};
974
975 #[derive(serde::Serialize)]
976 struct ClaimsWithIss {
977 sub: String,
978 exp: i64,
979 iss: String,
980 }
981
982 let secret = "test-secret";
983 let expected_issuer = "https://auth.example.com";
984 let config = AuthConfig::with_hs256(secret).with_issuer(expected_issuer);
985 let middleware = AuthMiddleware::from_config(config);
986
987 let now = chrono::Utc::now().timestamp();
989 let claims = ClaimsWithIss {
990 sub: "user123".to_string(),
991 exp: now + 3600,
992 iss: "https://wrong-issuer.com".to_string(),
993 };
994
995 let token =
996 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
997 .unwrap();
998
999 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1000 let result = middleware.validate_request(&req);
1001 let err = result.expect_err("expected error for wrong issuer");
1002 let msg = err.to_string();
1003
1004 assert!(
1005 msg.contains(expected_issuer),
1006 "error message must contain expected issuer '{expected_issuer}': {msg}"
1007 );
1008 assert!(msg.contains("issuer"), "error message must contain 'issuer': {msg}");
1009 }
1010
1011 #[test]
1012 #[allow(clippy::items_after_statements)] fn test_hs256_with_audience_validation() {
1014 use jsonwebtoken::{EncodingKey, Header, encode};
1015
1016 #[derive(serde::Serialize)]
1017 struct ClaimsWithAud {
1018 sub: String,
1019 exp: i64,
1020 aud: String,
1021 }
1022
1023 let secret = "test-secret";
1024 let config = AuthConfig::with_hs256(secret).with_audience("my-api");
1025 let middleware = AuthMiddleware::from_config(config);
1026
1027 let now = chrono::Utc::now().timestamp();
1029 let claims = ClaimsWithAud {
1030 sub: "user123".to_string(),
1031 exp: now + 3600,
1032 aud: "my-api".to_string(),
1033 };
1034
1035 let token =
1036 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
1037 .unwrap();
1038
1039 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1040 let result = middleware.validate_request(&req);
1041 assert!(result.is_ok(), "Expected valid token with audience, got: {:?}", result);
1042 }
1043
1044 #[test]
1045 fn test_signing_key_algorithm_detection() {
1046 let hs256 = SigningKey::hs256("secret");
1047 assert!(matches!(hs256.algorithm(), Algorithm::HS256));
1048
1049 let hs384 = SigningKey::Hs384(Zeroizing::new(b"secret".to_vec()));
1050 assert!(matches!(hs384.algorithm(), Algorithm::HS384));
1051
1052 let hs512 = SigningKey::Hs512(Zeroizing::new(b"secret".to_vec()));
1053 assert!(matches!(hs512.algorithm(), Algorithm::HS512));
1054
1055 let rs256_pem = SigningKey::rs256_pem("fake-pem");
1056 assert!(matches!(rs256_pem.algorithm(), Algorithm::RS256));
1057
1058 let rs256_comp = SigningKey::rs256_components("n", "e");
1059 assert!(matches!(rs256_comp.algorithm(), Algorithm::RS256));
1060 }
1061
1062 #[test]
1063 fn test_config_has_signing_key() {
1064 let config_without = AuthConfig::standard();
1065 assert!(!config_without.has_signing_key());
1066
1067 let config_with = AuthConfig::with_hs256("secret");
1068 assert!(config_with.has_signing_key());
1069 }
1070
1071 #[test]
1072 fn test_config_builder_pattern() {
1073 let config = AuthConfig::with_hs256("secret")
1074 .with_issuer("https://auth.example.com")
1075 .with_audience("my-api");
1076
1077 assert!(config.has_signing_key());
1078 assert_eq!(config.issuer, Some("https://auth.example.com".to_string()));
1079 assert_eq!(config.audience, Some("my-api".to_string()));
1080 }
1081
1082 #[test]
1083 fn test_malformed_token_rejected_with_signature_verification() {
1084 let config = AuthConfig::with_hs256("secret");
1085 let middleware = AuthMiddleware::from_config(config);
1086
1087 let req = AuthRequest::new(Some("Bearer not-a-jwt".to_string()));
1089 let result = middleware.validate_request(&req);
1090 assert!(
1091 matches!(result, Err(SecurityError::InvalidToken)),
1092 "Expected InvalidToken for malformed JWT, got: {:?}",
1093 result
1094 );
1095 }
1096
1097 #[test]
1098 fn test_tampered_payload_rejected() {
1099 let secret = "test-secret";
1100 let config = AuthConfig::with_hs256(secret);
1101 let middleware = AuthMiddleware::from_config(config);
1102
1103 let token = create_signed_hs256_token("user123", 3600, None, secret);
1105
1106 let parts: Vec<&str> = token.split('.').collect();
1108 let tampered_token = format!("{}.dGFtcGVyZWQ.{}", parts[0], parts[2]);
1109
1110 let req = AuthRequest::new(Some(format!("Bearer {tampered_token}")));
1111 let result = middleware.validate_request(&req);
1112 assert!(
1113 matches!(result, Err(SecurityError::JwtSignatureInvalid)),
1114 "Expected JwtSignatureInvalid for tampered payload, got: {:?}",
1115 result
1116 );
1117 }
1118
1119 #[test]
1120 fn test_clock_skew_tolerance() {
1121 let secret = "test-secret";
1122 let mut config = AuthConfig::with_hs256(secret);
1123 config.clock_skew_secs = 120; let middleware = AuthMiddleware::from_config(config);
1125
1126 let token = create_signed_hs256_token("user123", -30, None, secret);
1128 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1129
1130 let result = middleware.validate_request(&req);
1131 assert!(result.is_ok(), "Expected valid token within clock skew, got: {:?}", result);
1133 }
1134}