1use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use jsonwebtoken::{Validation, decode};
7
8use super::{
9 config::AuthConfig,
10 signing_key::SigningKey,
11 types::{AuthRequest, AuthenticatedUser, JwtClaims, TokenClaims},
12};
13use crate::security::errors::{Result, SecurityError};
14
15#[derive(Debug, Clone)]
20pub struct AuthMiddleware {
21 config: AuthConfig,
22}
23
24impl AuthMiddleware {
25 #[must_use]
30 pub fn from_config(config: AuthConfig) -> Self {
31 if config.required && config.signing_key.is_none() {
32 tracing::warn!(
33 "AuthMiddleware: required=true but no signing_key configured — \
34 JWT signatures will NOT be verified"
35 );
36 }
37 Self { config }
38 }
39
40 #[must_use]
42 pub fn permissive() -> Self {
43 Self::from_config(AuthConfig::permissive())
44 }
45
46 #[must_use]
48 pub fn standard() -> Self {
49 Self::from_config(AuthConfig::standard())
50 }
51
52 #[must_use]
54 pub fn strict() -> Self {
55 Self::from_config(AuthConfig::strict())
56 }
57
58 pub fn validate_request(&self, req: &AuthRequest) -> Result<AuthenticatedUser> {
75 let token = self.extract_token(req)?;
77
78 if let Some(ref signing_key) = self.config.signing_key {
80 self.validate_token_with_signature(&token, signing_key)
82 } else {
83 self.validate_token_structure_only(&token)
86 }
87 }
88
89 fn validate_token_with_signature(
93 &self,
94 token: &str,
95 signing_key: &SigningKey,
96 ) -> Result<AuthenticatedUser> {
97 let decoding_key = signing_key.to_decoding_key()?;
99
100 let mut validation = Validation::new(signing_key.algorithm());
102
103 if let Some(ref issuer) = self.config.issuer {
105 validation.set_issuer(&[issuer]);
106 }
107 if let Some(ref audience) = self.config.audience {
116 validation.set_audience(&[audience]);
117 }
118 validation.leeway = self.config.clock_skew_secs;
123
124 let token_data = decode::<JwtClaims>(token, &decoding_key, &validation).map_err(|e| {
126 match e.kind() {
127 jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
128 SecurityError::TokenExpired {
130 expired_at: Utc::now(), }
132 },
133 jsonwebtoken::errors::ErrorKind::InvalidSignature => {
134 SecurityError::JwtSignatureInvalid
135 },
136 jsonwebtoken::errors::ErrorKind::InvalidIssuer => {
137 SecurityError::JwtIssuerMismatch {
138 expected: self
139 .config
140 .issuer
141 .clone()
142 .unwrap_or_else(|| "(not configured)".to_string()),
143 }
144 },
145 jsonwebtoken::errors::ErrorKind::InvalidAudience => {
146 SecurityError::JwtAudienceMismatch {
147 expected: self
148 .config
149 .audience
150 .clone()
151 .unwrap_or_else(|| "(not configured)".to_string()),
152 }
153 },
154 jsonwebtoken::errors::ErrorKind::InvalidAlgorithm => {
155 SecurityError::InvalidTokenAlgorithm {
156 algorithm: format!("{:?}", signing_key.algorithm()),
157 }
158 },
159 jsonwebtoken::errors::ErrorKind::MissingRequiredClaim(claim) => {
160 SecurityError::TokenMissingClaim {
161 claim: claim.clone(),
162 }
163 },
164 _ => SecurityError::InvalidToken,
165 }
166 })?;
167
168 let claims = token_data.claims;
169
170 let scopes = self.extract_scopes_from_jwt_claims(&claims);
172
173 let user_id = claims.sub.ok_or(SecurityError::TokenMissingClaim {
175 claim: "sub".to_string(),
176 })?;
177
178 let exp = claims.exp.ok_or(SecurityError::TokenMissingClaim {
180 claim: "exp".to_string(),
181 })?;
182
183 let expires_at =
184 DateTime::<Utc>::from_timestamp(exp, 0).ok_or(SecurityError::InvalidToken)?;
185
186 Ok(AuthenticatedUser {
187 user_id,
188 scopes,
189 expires_at,
190 extra_claims: claims.extra,
191 })
192 }
193
194 fn extract_scopes_from_jwt_claims(&self, claims: &JwtClaims) -> Vec<String> {
201 if let Some(ref scope) = claims.scope {
203 return scope.split_whitespace().map(String::from).collect();
204 }
205
206 if let Some(ref scp) = claims.scp {
208 return scp.clone();
209 }
210
211 if let Some(ref permissions) = claims.permissions {
213 return permissions.clone();
214 }
215
216 Vec::new()
217 }
218
219 fn validate_token_structure_only(&self, token: &str) -> Result<AuthenticatedUser> {
224 self.validate_token_structure(token)?;
226
227 let claims = self.parse_claims(token)?;
229
230 let exp = claims.exp.ok_or(SecurityError::TokenMissingClaim {
232 claim: "exp".to_string(),
233 })?;
234
235 let expires_at =
237 DateTime::<Utc>::from_timestamp(exp, 0).ok_or(SecurityError::InvalidToken)?;
238
239 if expires_at <= Utc::now() {
240 return Err(SecurityError::TokenExpired {
241 expired_at: expires_at,
242 });
243 }
244
245 let user_id = claims.sub.ok_or(SecurityError::TokenMissingClaim {
247 claim: "sub".to_string(),
248 })?;
249
250 let scopes = claims
252 .scope
253 .as_ref()
254 .map(|s| s.split_whitespace().map(String::from).collect())
255 .unwrap_or_default();
256
257 Ok(AuthenticatedUser {
258 user_id,
259 scopes,
260 expires_at,
261 extra_claims: HashMap::new(),
262 })
263 }
264
265 fn extract_token(&self, req: &AuthRequest) -> Result<String> {
267 if !self.config.required && req.authorization_header.is_none() {
269 return Err(SecurityError::AuthRequired); }
271
272 req.extract_bearer_token()
273 }
274
275 fn validate_token_structure(&self, token: &str) -> Result<()> {
280 let parts: Vec<&str> = token.split('.').collect();
282 if parts.len() != 3 {
283 return Err(SecurityError::InvalidToken);
284 }
285
286 if parts.iter().any(|p| p.is_empty()) {
288 return Err(SecurityError::InvalidToken);
289 }
290
291 Ok(())
292 }
293
294 fn parse_claims(&self, token: &str) -> Result<TokenClaims> {
299 let parts: Vec<&str> = token.split('.').collect();
301 if parts.len() != 3 {
302 return Err(SecurityError::InvalidToken);
303 }
304
305 let payload_part = parts[1];
309
310 if let Ok(decoded) = hex::decode(payload_part) {
313 if let Ok(json_str) = std::str::from_utf8(&decoded) {
314 if let Ok(json) = serde_json::from_str::<serde_json::Value>(json_str) {
315 return Ok(self.extract_claims_from_json(&json));
316 }
317 }
318 }
319
320 if let Ok(json) = serde_json::from_str::<serde_json::Value>(payload_part) {
322 return Ok(self.extract_claims_from_json(&json));
323 }
324
325 Err(SecurityError::InvalidToken)
326 }
327
328 fn extract_claims_from_json(&self, json: &serde_json::Value) -> TokenClaims {
330 let sub = json["sub"].as_str().map(String::from);
331 let exp = json["exp"].as_i64();
332 let scope = json["scope"].as_str().map(String::from);
333 let aud = json["aud"]
334 .as_array()
335 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect());
336 let iss = json["iss"].as_str().map(String::from);
337
338 TokenClaims {
339 sub,
340 exp,
341 scope,
342 aud,
343 iss,
344 }
345 }
346
347 #[must_use]
349 pub const fn config(&self) -> &AuthConfig {
350 &self.config
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 #![allow(clippy::unwrap_used)] use chrono::Utc;
359 use jsonwebtoken::Algorithm;
360 use zeroize::Zeroizing;
361
362 use super::{
363 super::{
364 config::AuthConfig,
365 signing_key::SigningKey,
366 types::{AuthRequest, AuthenticatedUser},
367 },
368 *,
369 };
370 use crate::security::errors::SecurityError;
371
372 fn create_test_token(sub: &str, exp_offset_secs: i64, scope: Option<&str>) -> String {
381 let now = chrono::Utc::now().timestamp();
382 let exp = now + exp_offset_secs;
383
384 let mut payload = serde_json::json!({
386 "sub": sub,
387 "exp": exp,
388 "iat": now,
389 "aud": ["test-audience"],
390 "iss": "test-issuer"
391 });
392
393 if let Some(s) = scope {
394 payload["scope"] = serde_json::json!(s);
395 }
396
397 let payload_json = payload.to_string();
399 let payload_hex = hex::encode(&payload_json);
400 let signature = "test_signature"; format!("header.{payload_hex}.{signature}")
404 }
405
406 #[test]
411 fn test_bearer_token_extracted_correctly() {
412 let token = "test_token_12345";
413 let req = AuthRequest::new(Some(format!("Bearer {token}")));
414
415 let extracted = req
416 .extract_bearer_token()
417 .unwrap_or_else(|e| panic!("expected bearer token extraction to succeed: {e}"));
418 assert_eq!(extracted, token);
419 }
420
421 #[test]
422 fn test_missing_authorization_header_rejected() {
423 let req = AuthRequest::new(None);
424
425 let result = req.extract_bearer_token();
426 assert!(matches!(result, Err(SecurityError::AuthRequired)));
427 }
428
429 #[test]
430 fn test_invalid_authorization_header_format_rejected() {
431 let req = AuthRequest::new(Some("Basic abc123".to_string()));
432
433 let result = req.extract_bearer_token();
434 assert!(matches!(result, Err(SecurityError::AuthRequired)));
435 }
436
437 #[test]
438 fn test_bearer_prefix_required() {
439 let req = AuthRequest::new(Some("abc123".to_string()));
440
441 let result = req.extract_bearer_token();
442 assert!(matches!(result, Err(SecurityError::AuthRequired)));
443 }
444
445 #[test]
450 fn test_valid_token_structure() {
451 let middleware = AuthMiddleware::permissive();
452 let token = create_test_token("user123", 3600, None);
453
454 let result = middleware.validate_token_structure(&token);
455 result.unwrap_or_else(|e| panic!("expected valid token structure: {e}"));
456 }
457
458 #[test]
459 fn test_token_with_wrong_part_count_rejected() {
460 let middleware = AuthMiddleware::permissive();
461 let token = "header.payload"; let result = middleware.validate_token_structure(token);
464 assert!(matches!(result, Err(SecurityError::InvalidToken)));
465 }
466
467 #[test]
468 fn test_token_with_empty_part_rejected() {
469 let middleware = AuthMiddleware::permissive();
470 let token = "header..signature"; let result = middleware.validate_token_structure(token);
473 assert!(matches!(result, Err(SecurityError::InvalidToken)));
474 }
475
476 #[test]
481 fn test_valid_token_not_expired() {
482 let middleware = AuthMiddleware::standard();
483 let token = create_test_token("user123", 3600, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
485
486 let result = middleware.validate_request(&req);
487 result.unwrap_or_else(|e| panic!("expected valid non-expired token to pass: {e}"));
488 }
489
490 #[test]
491 fn test_expired_token_rejected() {
492 let middleware = AuthMiddleware::standard();
493 let token = create_test_token("user123", -3600, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
495
496 let result = middleware.validate_request(&req);
497 assert!(matches!(result, Err(SecurityError::TokenExpired { .. })));
498 }
499
500 #[test]
509 fn test_token_expired_one_second_ago_is_rejected() {
510 let middleware = AuthMiddleware::standard();
511 let token = create_test_token("user123", -1, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
513
514 assert!(
515 matches!(middleware.validate_request(&req), Err(SecurityError::TokenExpired { .. })),
516 "token expired 1s ago must be rejected"
517 );
518 }
519
520 #[test]
525 fn test_token_expiring_soon_is_accepted() {
526 let middleware = AuthMiddleware::standard();
527 let token = create_test_token("user123", 60, None); let req = AuthRequest::new(Some(format!("Bearer {token}")));
529
530 assert!(
531 middleware.validate_request(&req).is_ok(),
532 "token expiring in 60s must be accepted"
533 );
534 }
535
536 #[test]
541 fn test_missing_sub_claim_rejected() {
542 let middleware = AuthMiddleware::standard();
543
544 let now = chrono::Utc::now().timestamp();
546 let payload = serde_json::json!({
547 "exp": now + 3600,
548 "iat": now
549 });
550
551 let payload_hex = hex::encode(payload.to_string());
552 let token = format!("header.{payload_hex}.signature");
553
554 let req = AuthRequest::new(Some(format!("Bearer {token}")));
555 let result = middleware.validate_request(&req);
556
557 assert!(matches!(
558 result,
559 Err(SecurityError::TokenMissingClaim { claim })
560 if claim == "sub"
561 ));
562 }
563
564 #[test]
565 fn test_missing_exp_claim_rejected() {
566 let middleware = AuthMiddleware::standard();
567
568 let payload = serde_json::json!({
570 "sub": "user123",
571 "iat": chrono::Utc::now().timestamp()
572 });
573
574 let payload_hex = hex::encode(payload.to_string());
575 let token = format!("header.{payload_hex}.signature");
576
577 let req = AuthRequest::new(Some(format!("Bearer {token}")));
578 let result = middleware.validate_request(&req);
579
580 assert!(matches!(
581 result,
582 Err(SecurityError::TokenMissingClaim { claim })
583 if claim == "exp"
584 ));
585 }
586
587 #[test]
592 fn test_user_id_extracted_from_token() {
593 let middleware = AuthMiddleware::standard();
594 let token = create_test_token("user_12345", 3600, None);
595 let req = AuthRequest::new(Some(format!("Bearer {token}")));
596
597 let user = middleware
598 .validate_request(&req)
599 .unwrap_or_else(|e| panic!("expected user_id extraction to succeed: {e}"));
600 assert_eq!(user.user_id, "user_12345");
601 }
602
603 #[test]
604 fn test_scopes_extracted_from_token() {
605 let middleware = AuthMiddleware::standard();
606 let token = create_test_token("user123", 3600, Some("read write admin"));
607 let req = AuthRequest::new(Some(format!("Bearer {token}")));
608
609 let user = middleware
610 .validate_request(&req)
611 .unwrap_or_else(|e| panic!("expected scope extraction to succeed: {e}"));
612 assert_eq!(user.scopes, vec!["read", "write", "admin"]);
613 }
614
615 #[test]
616 fn test_empty_scopes_when_scope_claim_absent() {
617 let middleware = AuthMiddleware::standard();
618 let token = create_test_token("user123", 3600, None);
619 let req = AuthRequest::new(Some(format!("Bearer {token}")));
620
621 let user = middleware
622 .validate_request(&req)
623 .unwrap_or_else(|e| panic!("expected token without scopes to be valid: {e}"));
624 assert!(user.scopes.is_empty(), "expected empty scopes, got: {:?}", user.scopes);
625 }
626
627 #[test]
628 fn test_expires_at_extracted_correctly() {
629 let middleware = AuthMiddleware::standard();
630 let offset_secs = 7200; let token = create_test_token("user123", offset_secs, None);
633 let req = AuthRequest::new(Some(format!("Bearer {token}")));
634
635 let user = middleware
636 .validate_request(&req)
637 .unwrap_or_else(|e| panic!("expected expiry extraction to succeed: {e}"));
638 let now = Utc::now();
639 let diff = (user.expires_at - now).num_seconds();
640
641 assert!((offset_secs - 5..=offset_secs + 5).contains(&diff));
643 }
644
645 #[test]
650 fn test_permissive_config() {
651 let config = AuthConfig::permissive();
652 assert!(!config.required);
653 assert_eq!(config.token_expiry_secs, 3600);
654 }
655
656 #[test]
657 fn test_standard_config() {
658 let config = AuthConfig::standard();
659 assert!(config.required);
660 assert_eq!(config.token_expiry_secs, 3600);
661 }
662
663 #[test]
664 fn test_strict_config() {
665 let config = AuthConfig::strict();
666 assert!(config.required);
667 assert_eq!(config.token_expiry_secs, 1800);
668 }
669
670 #[test]
671 fn test_middleware_helpers() {
672 let permissive = AuthMiddleware::permissive();
673 assert!(!permissive.config().required);
674
675 let standard = AuthMiddleware::standard();
676 assert!(standard.config().required);
677
678 let strict = AuthMiddleware::strict();
679 assert!(strict.config().required);
680 }
681
682 #[test]
687 fn test_user_has_scope() {
688 let user = AuthenticatedUser {
689 user_id: "user123".to_string(),
690 scopes: vec!["read".to_string(), "write".to_string()],
691 expires_at: Utc::now() + chrono::Duration::hours(1),
692 extra_claims: HashMap::new(),
693 };
694
695 assert!(user.has_scope("read"));
696 assert!(user.has_scope("write"));
697 assert!(!user.has_scope("admin"));
698 }
699
700 #[test]
701 fn test_user_is_not_expired() {
702 let user = AuthenticatedUser {
703 user_id: "user123".to_string(),
704 scopes: vec![],
705 expires_at: Utc::now() + chrono::Duration::hours(1),
706 extra_claims: HashMap::new(),
707 };
708
709 assert!(!user.is_expired());
710 }
711
712 #[test]
713 fn test_user_is_expired() {
714 let user = AuthenticatedUser {
715 user_id: "user123".to_string(),
716 scopes: vec![],
717 expires_at: Utc::now() - chrono::Duration::hours(1),
718 extra_claims: HashMap::new(),
719 };
720
721 assert!(user.is_expired());
722 }
723
724 #[test]
725 fn test_user_ttl_calculation() {
726 let now = Utc::now();
727 let expires_at = now + chrono::Duration::hours(2);
728 let user = AuthenticatedUser {
729 user_id: "user123".to_string(),
730 scopes: vec![],
731 expires_at,
732 extra_claims: HashMap::new(),
733 };
734
735 let ttl = user.ttl_secs();
736 assert!((7195..=7205).contains(&ttl));
738 }
739
740 #[test]
741 fn test_user_display() {
742 let user = AuthenticatedUser {
743 user_id: "user123".to_string(),
744 scopes: vec![],
745 expires_at: Utc::now() + chrono::Duration::hours(1),
746 extra_claims: HashMap::new(),
747 };
748
749 let display_str = user.to_string();
750 assert!(display_str.contains("user123"));
751 assert!(display_str.contains("UTC"));
752 }
753
754 #[test]
759 fn test_error_messages_clear_and_actionable() {
760 let middleware = AuthMiddleware::standard();
761
762 let req = AuthRequest::new(None);
764 let result = middleware.validate_request(&req);
765 assert!(matches!(result, Err(SecurityError::AuthRequired)));
766
767 let req = AuthRequest::new(Some("Basic xyz".to_string()));
769 let result = middleware.validate_request(&req);
770 assert!(matches!(result, Err(SecurityError::AuthRequired)));
771 }
772
773 #[test]
778 fn test_auth_not_required_allows_missing_token() {
779 let middleware = AuthMiddleware::permissive(); let req = AuthRequest::new(None);
782
783 let result = middleware.validate_request(&req);
784 assert!(matches!(result, Err(SecurityError::AuthRequired)));
786 }
787
788 #[test]
789 fn test_whitespace_in_scopes_handled() {
790 let middleware = AuthMiddleware::standard();
791 let token = create_test_token("user123", 3600, Some(" read write admin "));
792 let req = AuthRequest::new(Some(format!("Bearer {token}")));
793
794 let user = middleware
795 .validate_request(&req)
796 .unwrap_or_else(|e| panic!("expected whitespace-heavy scopes to parse: {e}"));
797 assert_eq!(user.scopes.len(), 3);
799 }
800
801 #[test]
802 fn test_single_scope_parsed_correctly() {
803 let middleware = AuthMiddleware::standard();
804 let token = create_test_token("user123", 3600, Some("read"));
805 let req = AuthRequest::new(Some(format!("Bearer {token}")));
806
807 let user = middleware
808 .validate_request(&req)
809 .unwrap_or_else(|e| panic!("expected single scope to parse: {e}"));
810 assert_eq!(user.scopes, vec!["read"]);
811 }
812
813 #[allow(clippy::items_after_statements)] fn create_signed_hs256_token(
820 sub: &str,
821 exp_offset_secs: i64,
822 scope: Option<&str>,
823 secret: &str,
824 ) -> String {
825 use jsonwebtoken::{EncodingKey, Header, encode};
826
827 let now = chrono::Utc::now().timestamp();
828 let exp = now + exp_offset_secs;
829
830 #[derive(serde::Serialize)]
831 struct Claims {
832 sub: String,
833 exp: i64,
834 iat: i64,
835 #[serde(skip_serializing_if = "Option::is_none")]
836 scope: Option<String>,
837 }
838
839 let claims = Claims {
840 sub: sub.to_string(),
841 exp,
842 iat: now,
843 scope: scope.map(String::from),
844 };
845
846 encode(
847 &Header::default(), &claims,
849 &EncodingKey::from_secret(secret.as_bytes()),
850 )
851 .expect("Failed to create test token")
852 }
853
854 #[test]
855 fn test_hs256_signature_verification_valid_token() {
856 let secret = "super-secret-key-for-testing-only";
857 let config = AuthConfig::with_hs256(secret);
858 let middleware = AuthMiddleware::from_config(config);
859
860 let token = create_signed_hs256_token("user123", 3600, Some("read write"), secret);
861 let req = AuthRequest::new(Some(format!("Bearer {token}")));
862
863 let result = middleware.validate_request(&req);
864 assert!(result.is_ok(), "Expected valid token, got: {:?}", result);
865
866 let user = result.unwrap();
867 assert_eq!(user.user_id, "user123");
868 assert_eq!(user.scopes, vec!["read", "write"]);
869 }
870
871 #[test]
872 fn test_hs256_signature_verification_wrong_secret_rejected() {
873 let signing_secret = "correct-secret";
874 let wrong_secret = "wrong-secret";
875
876 let config = AuthConfig::with_hs256(signing_secret);
877 let middleware = AuthMiddleware::from_config(config);
878
879 let token = create_signed_hs256_token("user123", 3600, None, wrong_secret);
881 let req = AuthRequest::new(Some(format!("Bearer {token}")));
882
883 let result = middleware.validate_request(&req);
884 assert!(
885 matches!(result, Err(SecurityError::JwtSignatureInvalid)),
886 "Expected JwtSignatureInvalid for wrong signature, got: {:?}",
887 result
888 );
889 }
890
891 #[test]
892 fn test_hs256_expired_token_rejected() {
893 let secret = "test-secret";
894 let config = AuthConfig::with_hs256(secret);
895 let middleware = AuthMiddleware::from_config(config);
896
897 let token = create_signed_hs256_token("user123", -3600, None, secret);
899 let req = AuthRequest::new(Some(format!("Bearer {token}")));
900
901 let result = middleware.validate_request(&req);
902 assert!(
903 matches!(result, Err(SecurityError::TokenExpired { .. })),
904 "Expected TokenExpired, got: {:?}",
905 result
906 );
907 }
908
909 #[test]
910 #[allow(clippy::items_after_statements)] fn test_hs256_with_issuer_validation() {
912 use jsonwebtoken::{EncodingKey, Header, encode};
913
914 #[derive(serde::Serialize)]
915 struct ClaimsWithIss {
916 sub: String,
917 exp: i64,
918 iss: String,
919 }
920
921 let secret = "test-secret";
922 let config = AuthConfig::with_hs256(secret).with_issuer("https://auth.example.com");
923 let middleware = AuthMiddleware::from_config(config);
924
925 let now = chrono::Utc::now().timestamp();
927 let claims = ClaimsWithIss {
928 sub: "user123".to_string(),
929 exp: now + 3600,
930 iss: "https://auth.example.com".to_string(),
931 };
932
933 let token =
934 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
935 .unwrap();
936
937 let req = AuthRequest::new(Some(format!("Bearer {token}")));
938 let result = middleware.validate_request(&req);
939 assert!(result.is_ok(), "Expected valid token with issuer, got: {:?}", result);
940 }
941
942 #[test]
943 #[allow(clippy::items_after_statements)] fn test_hs256_with_wrong_issuer_rejected() {
945 use jsonwebtoken::{EncodingKey, Header, encode};
946
947 #[derive(serde::Serialize)]
948 struct ClaimsWithIss {
949 sub: String,
950 exp: i64,
951 iss: String,
952 }
953
954 let secret = "test-secret";
955 let config = AuthConfig::with_hs256(secret).with_issuer("https://auth.example.com");
956 let middleware = AuthMiddleware::from_config(config);
957
958 let now = chrono::Utc::now().timestamp();
960 let claims = ClaimsWithIss {
961 sub: "user123".to_string(),
962 exp: now + 3600,
963 iss: "https://wrong-issuer.com".to_string(),
964 };
965
966 let token =
967 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
968 .unwrap();
969
970 let req = AuthRequest::new(Some(format!("Bearer {token}")));
971 let result = middleware.validate_request(&req);
972 assert!(
973 matches!(result, Err(SecurityError::JwtIssuerMismatch { .. })),
974 "Expected JwtIssuerMismatch for wrong issuer, got: {:?}",
975 result
976 );
977 }
978
979 #[test]
980 #[allow(clippy::items_after_statements)] fn test_jwt_issuer_mismatch_error_contains_expected_issuer_and_word_issuer() {
982 use jsonwebtoken::{EncodingKey, Header, encode};
983
984 #[derive(serde::Serialize)]
985 struct ClaimsWithIss {
986 sub: String,
987 exp: i64,
988 iss: String,
989 }
990
991 let secret = "test-secret";
992 let expected_issuer = "https://auth.example.com";
993 let config = AuthConfig::with_hs256(secret).with_issuer(expected_issuer);
994 let middleware = AuthMiddleware::from_config(config);
995
996 let now = chrono::Utc::now().timestamp();
998 let claims = ClaimsWithIss {
999 sub: "user123".to_string(),
1000 exp: now + 3600,
1001 iss: "https://wrong-issuer.com".to_string(),
1002 };
1003
1004 let token =
1005 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
1006 .unwrap();
1007
1008 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1009 let result = middleware.validate_request(&req);
1010 let err = result.expect_err("expected error for wrong issuer");
1011 let msg = err.to_string();
1012
1013 assert!(
1014 msg.contains(expected_issuer),
1015 "error message must contain expected issuer '{expected_issuer}': {msg}"
1016 );
1017 assert!(msg.contains("issuer"), "error message must contain 'issuer': {msg}");
1018 }
1019
1020 #[test]
1021 #[allow(clippy::items_after_statements)] fn test_hs256_with_audience_validation() {
1023 use jsonwebtoken::{EncodingKey, Header, encode};
1024
1025 #[derive(serde::Serialize)]
1026 struct ClaimsWithAud {
1027 sub: String,
1028 exp: i64,
1029 aud: String,
1030 }
1031
1032 let secret = "test-secret";
1033 let config = AuthConfig::with_hs256(secret).with_audience("my-api");
1034 let middleware = AuthMiddleware::from_config(config);
1035
1036 let now = chrono::Utc::now().timestamp();
1038 let claims = ClaimsWithAud {
1039 sub: "user123".to_string(),
1040 exp: now + 3600,
1041 aud: "my-api".to_string(),
1042 };
1043
1044 let token =
1045 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
1046 .unwrap();
1047
1048 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1049 let result = middleware.validate_request(&req);
1050 assert!(result.is_ok(), "Expected valid token with audience, got: {:?}", result);
1051 }
1052
1053 #[test]
1054 fn test_signing_key_algorithm_detection() {
1055 let hs256 = SigningKey::hs256("secret");
1056 assert!(matches!(hs256.algorithm(), Algorithm::HS256));
1057
1058 let hs384 = SigningKey::Hs384(Zeroizing::new(b"secret".to_vec()));
1059 assert!(matches!(hs384.algorithm(), Algorithm::HS384));
1060
1061 let hs512 = SigningKey::Hs512(Zeroizing::new(b"secret".to_vec()));
1062 assert!(matches!(hs512.algorithm(), Algorithm::HS512));
1063
1064 let rs256_pem = SigningKey::rs256_pem("fake-pem");
1065 assert!(matches!(rs256_pem.algorithm(), Algorithm::RS256));
1066
1067 let rs256_comp = SigningKey::rs256_components("n", "e");
1068 assert!(matches!(rs256_comp.algorithm(), Algorithm::RS256));
1069 }
1070
1071 #[test]
1072 fn test_config_has_signing_key() {
1073 let config_without = AuthConfig::standard();
1074 assert!(!config_without.has_signing_key());
1075
1076 let config_with = AuthConfig::with_hs256("secret");
1077 assert!(config_with.has_signing_key());
1078 }
1079
1080 #[test]
1081 fn test_config_builder_pattern() {
1082 let config = AuthConfig::with_hs256("secret")
1083 .with_issuer("https://auth.example.com")
1084 .with_audience("my-api");
1085
1086 assert!(config.has_signing_key());
1087 assert_eq!(config.issuer, Some("https://auth.example.com".to_string()));
1088 assert_eq!(config.audience, Some("my-api".to_string()));
1089 }
1090
1091 #[test]
1092 fn test_malformed_token_rejected_with_signature_verification() {
1093 let config = AuthConfig::with_hs256("secret");
1094 let middleware = AuthMiddleware::from_config(config);
1095
1096 let req = AuthRequest::new(Some("Bearer not-a-jwt".to_string()));
1098 let result = middleware.validate_request(&req);
1099 assert!(
1100 matches!(result, Err(SecurityError::InvalidToken)),
1101 "Expected InvalidToken for malformed JWT, got: {:?}",
1102 result
1103 );
1104 }
1105
1106 #[test]
1107 fn test_tampered_payload_rejected() {
1108 let secret = "test-secret";
1109 let config = AuthConfig::with_hs256(secret);
1110 let middleware = AuthMiddleware::from_config(config);
1111
1112 let token = create_signed_hs256_token("user123", 3600, None, secret);
1114
1115 let parts: Vec<&str> = token.split('.').collect();
1117 let tampered_token = format!("{}.dGFtcGVyZWQ.{}", parts[0], parts[2]);
1118
1119 let req = AuthRequest::new(Some(format!("Bearer {tampered_token}")));
1120 let result = middleware.validate_request(&req);
1121 assert!(
1122 matches!(result, Err(SecurityError::JwtSignatureInvalid)),
1123 "Expected JwtSignatureInvalid for tampered payload, got: {:?}",
1124 result
1125 );
1126 }
1127
1128 #[test]
1129 fn test_clock_skew_tolerance() {
1130 let secret = "test-secret";
1131 let mut config = AuthConfig::with_hs256(secret);
1132 config.clock_skew_secs = 120; let middleware = AuthMiddleware::from_config(config);
1134
1135 let token = create_signed_hs256_token("user123", -30, None, secret);
1137 let req = AuthRequest::new(Some(format!("Bearer {token}")));
1138
1139 let result = middleware.validate_request(&req);
1140 assert!(result.is_ok(), "Expected valid token within clock skew, got: {:?}", result);
1142 }
1143}