1use async_trait::async_trait;
14use jsonwebtoken::{decode, decode_header, jwk::JwkSet, Algorithm, DecodingKey, Validation};
15use jsonwebtoken::jwk::{AlgorithmParameters, Jwk, OctetKeyParameters};
16use reqwest::Client;
17use std::{sync::Arc, time::Duration};
18use serde::Deserialize;
19use tokio::sync::RwLock;
20use globset::{Glob, GlobSet, GlobSetBuilder};
21use crate::{core::{ProxyError, ProxyRequest}, debug_fmt, error_fmt, security::{SecurityProvider, SecurityStage}, trace_fmt, warn_fmt};
22
23pub const CLAIMS_ATTRIBUTE: &str = "oidc-claims";
24const BEARER: &str = "bearer ";
25const JWKS_REFRESH: Duration = Duration::from_secs(30 * 60);
26
27#[derive(Debug, Clone, serde::Deserialize)]
28pub struct RouteRuleConfig {
29 pub methods: Vec<String>,
30 pub path: String,
31}
32
33#[derive(Debug)]
34struct RouteRule {
35 methods: Vec<String>,
36 paths: GlobSet,
37}
38
39impl RouteRule {
40 fn matches(&self, method: &str, path: &str) -> bool {
41 let method_match = self.methods.iter().any(|m| m == "*" || m == method);
42 let path_match = self.paths.is_match(path);
43
44 trace_fmt!("OidcProvider", "OIDC bypass rule check: method={} path={} -> method_match={} path_match={}",
45 method, path, method_match, path_match);
46
47 method_match && path_match
48 }
49}
50
51#[derive(Debug, Clone, Deserialize)]
53pub struct OidcConfig {
54 #[serde(rename = "issuer-uri")]
55 pub issuer_uri: String,
56
57 pub aud: Option<String>,
59
60 #[serde(rename = "shared-secret")]
62 pub shared_secret: Option<String>,
63
64 #[serde(default)]
66 pub bypass: Vec<RouteRuleConfig>,
67}
68
69#[derive(Debug)]
71pub struct OidcProvider {
72 issuer: String,
74
75 aud: Option<String>,
77
78 shared_secret: Option<String>,
80
81 jwks_uri: String,
83
84 jwks: Arc<RwLock<Option<JwkSet>>>,
86
87 last_refresh: Arc<RwLock<tokio::time::Instant>>,
89
90 http: Client,
92
93 rules: Vec<RouteRule>,
95}
96
97impl OidcProvider {
98 pub async fn discover(cfg: OidcConfig) -> Result<Self, ProxyError> {
100 debug_fmt!("OidcProvider", "OIDC discovery from {}", cfg.issuer_uri);
102
103 let client = Client::builder()
104 .user_agent("foxy/oidc")
105 .build()
106 .map_err(|e| {
107 let err = ProxyError::SecurityError(format!("Failed to build HTTP client: {e}"));
108 error_fmt!("OidcProvider", "{}", err);
109 err
110 })?;
111
112 #[derive(Deserialize)]
113 struct Discovery { jwks_uri: String }
114
115 let meta: Discovery = match client.get(&cfg.issuer_uri).send().await {
116 Ok(response) => {
117 match response.error_for_status() {
118 Ok(response) => {
119 match response.json().await {
120 Ok(meta) => meta,
121 Err(e) => {
122 let err = ProxyError::SecurityError(
123 format!("Failed to parse OIDC discovery response: {e}")
124 );
125 error_fmt!("OidcProvider", "{}", err);
126 return Err(err);
127 }
128 }
129 },
130 Err(e) => {
131 let err = ProxyError::SecurityError(
132 format!("OIDC discovery endpoint returned error: {e}")
133 );
134 error_fmt!("OidcProvider", "{}", err);
135 return Err(err);
136 }
137 }
138 },
139 Err(e) => {
140 let err = ProxyError::SecurityError(
141 format!("Failed to connect to OIDC discovery endpoint: {e}")
142 );
143 error_fmt!("OidcProvider", "{}", err);
144 return Err(err);
145 }
146 };
147
148 debug_fmt!("OidcProvider", "OIDC discovery successful, JWKS URI: {}", meta.jwks_uri);
149
150 let mut rules = Vec::with_capacity(cfg.bypass.len());
152 for raw in cfg.bypass {
153 let mut builder = GlobSetBuilder::new();
154 match Glob::new(&raw.path) {
155 Ok(glob) => {
156 builder.add(glob);
157 rules.push(RouteRule {
158 methods: raw.methods.iter().map(|m| m.to_ascii_uppercase()).collect(),
159 paths: match builder.build() {
160 Ok(set) => set,
161 Err(e) => {
162 let err = ProxyError::SecurityError(
163 format!("Failed to build glob set for path {}: {}", raw.path, e)
164 );
165 error_fmt!("OidcProvider", "{}", err);
166 return Err(err);
167 }
168 },
169 });
170 debug_fmt!("OidcProvider", "Added OIDC bypass rule: methods={:?}, path={}", raw.methods, raw.path);
171 },
172 Err(e) => {
173 let err = ProxyError::SecurityError(
174 format!("Invalid glob pattern in bypass rule: {e}")
175 );
176 error_fmt!("OidcProvider", "{}", err);
177 return Err(err);
178 }
179 }
180 }
181
182 Ok(Self {
183 issuer: cfg
184 .issuer_uri
185 .trim_end_matches("/.well-known/openid-configuration")
186 .to_owned(),
187 aud: cfg.aud,
188 shared_secret: cfg.shared_secret,
189 jwks_uri: meta.jwks_uri,
190 jwks: Arc::new(RwLock::new(None)),
191 last_refresh: Arc::new(RwLock::new(
192 tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
193 .unwrap_or_else(|| {
194 tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
196 .unwrap_or_else(|| tokio::time::Instant::now())
197 }),
198 )),
199 http: client,
200 rules,
201 })
202 }
203
204 async fn refresh_jwks(&self) -> Result<(), ProxyError> {
207 let now = tokio::time::Instant::now();
208
209 let should_refresh = {
211 let jwks_guard = self.jwks.read().await;
212 let cache_empty = jwks_guard.is_none();
213 let cache_expired = now.duration_since(*self.last_refresh.read().await) >= JWKS_REFRESH;
214 cache_empty || cache_expired
215 };
216
217 if !should_refresh {
218 trace_fmt!("OidcProvider", "JWKS cache still fresh, skipping refresh");
219 return Ok(());
220 }
221
222 debug_fmt!("OidcProvider", "Refreshing JWKS from {}", self.jwks_uri);
223
224 let jwks = match self.http.get(&self.jwks_uri).send().await {
226 Ok(response) => {
227 match response.error_for_status() {
228 Ok(response) => {
229 match response.json::<JwkSet>().await {
230 Ok(jwks) => jwks,
231 Err(e) => {
232 let err = ProxyError::SecurityError(
233 format!("Failed to parse JWKS response: {e}")
234 );
235 error_fmt!("OidcProvider", "{}", err);
236 return Err(err);
237 }
238 }
239 },
240 Err(e) => {
241 let err = ProxyError::SecurityError(
242 format!("JWKS endpoint returned error: {e}")
243 );
244 error_fmt!("OidcProvider", "{}", err);
245 return Err(err);
246 }
247 }
248 },
249 Err(e) => {
250 let err = ProxyError::SecurityError(
251 format!("Failed to connect to JWKS endpoint: {e}")
252 );
253 error_fmt!("OidcProvider", "{}", err);
254 return Err(err);
255 }
256 };
257
258 debug_fmt!("OidcProvider", "JWKS refresh successful, found {} keys", jwks.keys.len());
259
260 {
262 let mut w = self.jwks.write().await;
263 *w = Some(jwks);
264 }
265 {
266 let mut w = self.last_refresh.write().await;
267 *w = now;
268 }
269
270 Ok(())
271 }
272
273 fn jwk_to_decoding_key(&self, jwk: &Jwk) -> Result<DecodingKey, ProxyError> {
274 match &jwk.algorithm {
275 AlgorithmParameters::RSA(params) => {
276 trace_fmt!("OidcProvider", "Converting RSA JWK to decoding key");
277 DecodingKey::from_rsa_components(¶ms.n, ¶ms.e)
278 .map_err(|e| {
279 let err = ProxyError::SecurityError(format!("Invalid RSA key: {e}"));
280 error_fmt!("OidcProvider", "{}", err);
281 err
282 })
283 }
284 AlgorithmParameters::EllipticCurve(params) => {
285 trace_fmt!("OidcProvider", "Converting EC JWK to decoding key");
286 DecodingKey::from_ec_components(¶ms.x, ¶ms.y)
287 .map_err(|e| {
288 let err = ProxyError::SecurityError(format!("Invalid EC key: {e}"));
289 error_fmt!("OidcProvider", "{}", err);
290 err
291 })
292 }
293 AlgorithmParameters::OctetKey(OctetKeyParameters { value, .. }) => {
294 trace_fmt!("OidcProvider", "Converting octet JWK to decoding key");
295 Ok(DecodingKey::from_secret(value.as_bytes()))
296 }
297 AlgorithmParameters::OctetKeyPair(params) => {
298 trace_fmt!("OidcProvider", "Converting OKP JWK to decoding key");
299 DecodingKey::from_ed_components(¶ms.x)
300 .map_err(|e| {
301 let err = ProxyError::SecurityError(format!("Invalid OKP key: {e}"));
302 error_fmt!("OidcProvider", "{}", err);
303 err
304 })
305 }
306 }
307 }
308
309 async fn validate_token(&self, token: &str) -> Result<serde_json::Value, ProxyError> {
310 let header = match decode_header(token) {
312 Ok(h) => h,
313 Err(e) => {
314 let err = ProxyError::SecurityError(format!("Invalid JWT header: {e}"));
315 warn_fmt!("OidcProvider", "{}", err);
316 return Err(err);
317 }
318 };
319
320 trace_fmt!("OidcProvider", "JWT header: alg={:?}, kid={:?}", header.alg, header.kid);
321
322 let allowed_algs = [
324 Algorithm::RS256, Algorithm::RS384, Algorithm::RS512,
325 Algorithm::PS256, Algorithm::PS384, Algorithm::PS512,
326 Algorithm::ES256, Algorithm::ES384,
327 Algorithm::EdDSA,
328 Algorithm::HS256, Algorithm::HS384, Algorithm::HS512,
329 ];
330
331 if !allowed_algs.contains(&header.alg) {
332 let err = ProxyError::SecurityError(
333 format!("Algorithm not allowed: {:?}", header.alg)
334 );
335 warn_fmt!("OidcProvider", "{}", err);
336 return Err(err);
337 }
338
339 let key = match &header.kid {
341 Some(kid) => {
342 self.refresh_jwks().await?;
344
345 let jwks = self.jwks.read().await;
347 let jwks = match &*jwks {
348 Some(j) => j,
349 None => {
350 let err = ProxyError::SecurityError("No JWKS available".to_string());
351 error_fmt!("OidcProvider", "{}", err);
352 return Err(err);
353 }
354 };
355
356 match jwks.keys.iter().find(|k| k.common.key_id == Some(kid.clone())) {
358 Some(key) => {
359 trace_fmt!("OidcProvider", "Found key with ID {}", kid);
360 match self.jwk_to_decoding_key(key) {
361 Ok(key) => key,
362 Err(e) => {
363 error_fmt!("OidcProvider", "Failed to convert JWK to decoding key: {}", e);
364 return Err(e);
365 }
366 }
367 }
368 None => {
369 if let Some(ref secret) = self.shared_secret {
371 if matches!(header.alg, Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512) {
372 trace_fmt!("OidcProvider", "Using shared secret for HS* algorithm");
373 DecodingKey::from_secret(secret.as_bytes())
374 } else {
375 let err = ProxyError::SecurityError(format!("Key ID {kid} not found in JWKS"));
376 warn_fmt!("OidcProvider", "{}", err);
377 return Err(err);
378 }
379 } else {
380 let err = ProxyError::SecurityError(format!("Key ID {kid} not found in JWKS"));
381 warn_fmt!("OidcProvider", "{}", err);
382 return Err(err);
383 }
384 }
385 }
386 }
387 None => {
388 if let Some(ref secret) = self.shared_secret {
390 trace_fmt!("OidcProvider", "No key ID in token, using shared secret");
391 DecodingKey::from_secret(secret.as_bytes())
392 } else {
393 let err = ProxyError::SecurityError("No key ID in token and no shared secret configured".to_string());
394 warn_fmt!("OidcProvider", "{}", err);
395 return Err(err);
396 }
397 }
398 };
399
400 let mut validation = Validation::new(header.alg);
402 validation.set_audience(&[&self.aud.clone().unwrap_or_default()]);
403 validation.set_issuer(&[&self.issuer]);
404
405 match decode::<serde_json::Value>(token, &key, &validation) {
407 Ok(token_data) => {
408 debug_fmt!("OidcProvider", "JWT validation successful");
409 Ok(token_data.claims)
410 }
411 Err(e) => {
412 let err = ProxyError::SecurityError(format!("JWT validation failed: {e}"));
413 warn_fmt!("OidcProvider", "{}", err);
414 Err(err)
415 }
416 }
417 }
418
419 #[allow(dead_code)]
420 fn validate_std_claims(&self, claims: &serde_json::Value) -> Result<(), ProxyError> {
421 if let Some(iss) = claims["iss"].as_str() {
423 if iss != self.issuer {
424 let err = ProxyError::SecurityError(
425 format!("Invalid issuer: expected '{}', got '{}'", self.issuer, iss)
426 );
427 warn_fmt!("OidcProvider", "{}", err);
428 return Err(err);
429 }
430 } else {
431 let err = ProxyError::SecurityError("Missing issuer claim".to_string());
432 warn_fmt!("OidcProvider", "{}", err);
433 return Err(err);
434 }
435
436 if let Some(ref expected_aud) = self.aud {
438 let valid_audience = match &claims["aud"] {
439 serde_json::Value::String(aud) => aud == expected_aud,
440 serde_json::Value::Array(auds) => auds.iter()
441 .filter_map(|a| a.as_str())
442 .any(|a| a == expected_aud),
443 _ => false,
444 };
445
446 if !valid_audience {
447 let err = ProxyError::SecurityError(
448 format!("Invalid audience: expected '{expected_aud}'")
449 );
450 warn_fmt!("OidcProvider", "{}", err);
451 return Err(err);
452 }
453 }
454
455 if let Some(exp) = claims["exp"].as_i64() {
457 let now = std::time::SystemTime::now()
458 .duration_since(std::time::UNIX_EPOCH)
459 .unwrap_or_default()
460 .as_secs() as i64;
461
462 if exp <= now {
463 let err = ProxyError::SecurityError(
464 format!("Token expired at {exp}, current time is {now}")
465 );
466 warn_fmt!("OidcProvider", "{}", err);
467 return Err(err);
468 }
469 }
470
471 debug_fmt!("OidcProvider", "Token claims validation successful");
472 Ok(())
473 }
474
475 #[inline]
476 fn is_bypassed(&self, method: &str, path: &str) -> bool {
477 let bypassed = self.rules.iter().any(|r| r.matches(method, path));
478 if bypassed {
479 debug_fmt!("OidcProvider", "OIDC bypass for {} {}", method, path);
480 }
481 bypassed
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488 use crate::core::{ProxyRequest, HttpMethod, RequestContext};
489 use reqwest::header::HeaderMap;
490 use std::sync::Arc;
491 use tokio::sync::RwLock;
492 use wiremock::{MockServer, Mock, ResponseTemplate};
493 use wiremock::matchers::{method, path};
494 use base64::{Engine as _};
495
496 #[test]
497 fn test_route_rule_config_deserialization() {
498 let json = r#"{
499 "methods": ["GET", "POST"],
500 "path": "/api/*"
501 }"#;
502
503 let config: RouteRuleConfig = serde_json::from_str(json).unwrap();
504 assert_eq!(config.methods, vec!["GET", "POST"]);
505 assert_eq!(config.path, "/api/*");
506 }
507
508 #[test]
509 fn test_route_rule_matches() {
510 let mut builder = GlobSetBuilder::new();
511 builder.add(Glob::new("/api/*").unwrap());
512 let paths = builder.build().unwrap();
513
514 let rule = RouteRule {
515 methods: vec!["GET".to_string(), "POST".to_string()],
516 paths,
517 };
518
519 assert!(rule.matches("GET", "/api/users"));
521 assert!(rule.matches("POST", "/api/users"));
522 assert!(!rule.matches("DELETE", "/api/users"));
523 assert!(!rule.matches("GET", "/health"));
524
525 assert!(!rule.matches("get", "/api/users"));
527 assert!(!rule.matches("post", "/api/users"));
528 }
529
530 #[test]
531 fn test_route_rule_wildcard_methods() {
532 let mut builder = GlobSetBuilder::new();
533 builder.add(Glob::new("/health").unwrap());
534 let paths = builder.build().unwrap();
535
536 let rule = RouteRule {
537 methods: vec!["*".to_string()],
538 paths,
539 };
540
541 assert!(rule.matches("GET", "/health"));
542 assert!(rule.matches("POST", "/health"));
543 assert!(rule.matches("DELETE", "/health"));
544 assert!(!rule.matches("GET", "/api"));
545 }
546
547 #[test]
548 fn test_oidc_config_deserialization() {
549 let json = r#"{
550 "issuer-uri": "https://auth.example.com/.well-known/openid-configuration",
551 "aud": "my-app",
552 "shared-secret": "secret123",
553 "bypass": [
554 {
555 "methods": ["GET"],
556 "path": "/health"
557 }
558 ]
559 }"#;
560
561 let config: OidcConfig = serde_json::from_str(json).unwrap();
562 assert_eq!(config.issuer_uri, "https://auth.example.com/.well-known/openid-configuration");
563 assert_eq!(config.aud, Some("my-app".to_string()));
564 assert_eq!(config.shared_secret, Some("secret123".to_string()));
565 assert_eq!(config.bypass.len(), 1);
566 assert_eq!(config.bypass[0].methods, vec!["GET"]);
567 assert_eq!(config.bypass[0].path, "/health");
568 }
569
570 #[test]
571 fn test_oidc_config_minimal() {
572 let json = r#"{
573 "issuer-uri": "https://auth.example.com"
574 }"#;
575
576 let config: OidcConfig = serde_json::from_str(json).unwrap();
577 assert_eq!(config.issuer_uri, "https://auth.example.com");
578 assert_eq!(config.aud, None);
579 assert_eq!(config.shared_secret, None);
580 assert!(config.bypass.is_empty());
581 }
582
583 #[test]
584 fn test_oidc_config_empty_bypass() {
585 let json = r#"{
586 "issuer-uri": "https://auth.example.com",
587 "bypass": []
588 }"#;
589
590 let config: OidcConfig = serde_json::from_str(json).unwrap();
591 assert!(config.bypass.is_empty());
592 }
593
594 #[tokio::test]
595 async fn test_oidc_provider_discover_success() {
596 let mock_server = MockServer::start().await;
598
599 Mock::given(method("GET"))
601 .and(path("/"))
602 .respond_with(ResponseTemplate::new(200)
603 .set_body_json(serde_json::json!({
604 "issuer": mock_server.uri(),
605 "jwks_uri": format!("{}/jwks", mock_server.uri()),
606 "authorization_endpoint": format!("{}/auth", mock_server.uri()),
607 "token_endpoint": format!("{}/token", mock_server.uri())
608 })))
609 .mount(&mock_server)
610 .await;
611
612 let config = OidcConfig {
613 issuer_uri: mock_server.uri(),
614 aud: Some("test-audience".to_string()),
615 shared_secret: Some("test-secret".to_string()),
616 bypass: vec![
617 RouteRuleConfig {
618 methods: vec!["GET".to_string()],
619 path: "/health".to_string(),
620 },
621 RouteRuleConfig {
622 methods: vec!["*".to_string()],
623 path: "/public/*".to_string(),
624 },
625 ],
626 };
627
628 let result = OidcProvider::discover(config.clone()).await;
629 assert!(result.is_ok());
630
631 let provider = result.unwrap();
632 assert_eq!(provider.issuer, mock_server.uri());
633 assert_eq!(provider.aud, Some("test-audience".to_string()));
634 assert_eq!(provider.shared_secret, Some("test-secret".to_string()));
635 assert_eq!(provider.jwks_uri, format!("{}/jwks", mock_server.uri()));
636 assert_eq!(provider.rules.len(), 2);
637
638 assert!(provider.is_bypassed("GET", "/health"));
640 assert!(provider.is_bypassed("POST", "/public/api"));
641 assert!(!provider.is_bypassed("POST", "/private/api"));
642 }
643
644 #[tokio::test]
645 async fn test_oidc_provider_discover_success_minimal_config() {
646 let mock_server = MockServer::start().await;
648
649 Mock::given(method("GET"))
651 .and(path("/"))
652 .respond_with(ResponseTemplate::new(200)
653 .set_body_json(serde_json::json!({
654 "issuer": mock_server.uri(),
655 "jwks_uri": format!("{}/jwks", mock_server.uri())
656 })))
657 .mount(&mock_server)
658 .await;
659
660 let config = OidcConfig {
661 issuer_uri: mock_server.uri(),
662 aud: None,
663 shared_secret: None,
664 bypass: vec![],
665 };
666
667 let result = OidcProvider::discover(config).await;
668 assert!(result.is_ok());
669
670 let provider = result.unwrap();
671 assert_eq!(provider.issuer, mock_server.uri());
672 assert_eq!(provider.aud, None);
673 assert_eq!(provider.shared_secret, None);
674 assert_eq!(provider.jwks_uri, format!("{}/jwks", mock_server.uri()));
675 assert!(provider.rules.is_empty());
676 }
677
678 #[tokio::test]
679 async fn test_oidc_provider_discover_success_with_well_known_suffix() {
680 let mock_server = MockServer::start().await;
682
683 Mock::given(method("GET"))
685 .and(path("/.well-known/openid-configuration"))
686 .respond_with(ResponseTemplate::new(200)
687 .set_body_json(serde_json::json!({
688 "issuer": mock_server.uri(),
689 "jwks_uri": format!("{}/jwks", mock_server.uri())
690 })))
691 .mount(&mock_server)
692 .await;
693
694 let config = OidcConfig {
695 issuer_uri: format!("{}/.well-known/openid-configuration", mock_server.uri()),
696 aud: None,
697 shared_secret: None,
698 bypass: vec![],
699 };
700
701 let result = OidcProvider::discover(config).await;
702 assert!(result.is_ok());
703
704 let provider = result.unwrap();
705 assert_eq!(provider.issuer, mock_server.uri());
707 assert_eq!(provider.jwks_uri, format!("{}/jwks", mock_server.uri()));
708 }
709
710 #[tokio::test]
711 async fn test_oidc_provider_discover_invalid_url() {
712 let config = OidcConfig {
713 issuer_uri: "invalid-url".to_string(),
714 aud: None,
715 shared_secret: None,
716 bypass: vec![],
717 };
718
719 let result = OidcProvider::discover(config).await;
720 assert!(result.is_err());
721
722 if let Err(ProxyError::SecurityError(msg)) = result {
723 assert!(msg.contains("Failed to connect to OIDC discovery endpoint"));
724 } else {
725 panic!("Expected SecurityError");
726 }
727 }
728
729 #[tokio::test]
730 async fn test_oidc_provider_discover_http_error() {
731 let mock_server = MockServer::start().await;
733
734 Mock::given(method("GET"))
736 .and(path("/"))
737 .respond_with(ResponseTemplate::new(404))
738 .mount(&mock_server)
739 .await;
740
741 let config = OidcConfig {
742 issuer_uri: mock_server.uri(),
743 aud: None,
744 shared_secret: None,
745 bypass: vec![],
746 };
747
748 let result = OidcProvider::discover(config).await;
749 assert!(result.is_err());
750
751 if let Err(ProxyError::SecurityError(msg)) = result {
752 assert!(msg.contains("OIDC discovery endpoint returned error"));
753 } else {
754 panic!("Expected SecurityError");
755 }
756 }
757
758 #[tokio::test]
759 async fn test_oidc_provider_discover_invalid_json() {
760 let mock_server = MockServer::start().await;
762
763 Mock::given(method("GET"))
765 .and(path("/"))
766 .respond_with(ResponseTemplate::new(200)
767 .set_body_string("invalid json"))
768 .mount(&mock_server)
769 .await;
770
771 let config = OidcConfig {
772 issuer_uri: mock_server.uri(),
773 aud: None,
774 shared_secret: None,
775 bypass: vec![],
776 };
777
778 let result = OidcProvider::discover(config).await;
779 assert!(result.is_err());
780
781 if let Err(ProxyError::SecurityError(msg)) = result {
782 assert!(msg.contains("Failed to parse OIDC discovery response"));
783 } else {
784 panic!("Expected SecurityError");
785 }
786 }
787
788 #[tokio::test]
789 async fn test_oidc_provider_discover_missing_jwks_uri() {
790 let mock_server = MockServer::start().await;
792
793 Mock::given(method("GET"))
795 .and(path("/"))
796 .respond_with(ResponseTemplate::new(200)
797 .set_body_json(serde_json::json!({
798 "issuer": mock_server.uri(),
799 "authorization_endpoint": format!("{}/auth", mock_server.uri())
800 })))
801 .mount(&mock_server)
802 .await;
803
804 let config = OidcConfig {
805 issuer_uri: mock_server.uri(),
806 aud: None,
807 shared_secret: None,
808 bypass: vec![],
809 };
810
811 let result = OidcProvider::discover(config).await;
812 assert!(result.is_err());
813
814 if let Err(ProxyError::SecurityError(msg)) = result {
815 assert!(msg.contains("Failed to parse OIDC discovery response"));
816 } else {
817 panic!("Expected SecurityError");
818 }
819 }
820
821 #[tokio::test]
822 async fn test_oidc_provider_discover_invalid_bypass_glob() {
823 let mock_server = MockServer::start().await;
825
826 Mock::given(method("GET"))
828 .and(path("/"))
829 .respond_with(ResponseTemplate::new(200)
830 .set_body_json(serde_json::json!({
831 "issuer": mock_server.uri(),
832 "jwks_uri": format!("{}/jwks", mock_server.uri())
833 })))
834 .mount(&mock_server)
835 .await;
836
837 let config = OidcConfig {
838 issuer_uri: mock_server.uri(),
839 aud: None,
840 shared_secret: None,
841 bypass: vec![
842 RouteRuleConfig {
843 methods: vec!["GET".to_string()],
844 path: "[invalid-glob".to_string(), },
846 ],
847 };
848
849 let result = OidcProvider::discover(config).await;
850 assert!(result.is_err());
851
852 if let Err(ProxyError::SecurityError(msg)) = result {
853 assert!(msg.contains("Invalid glob pattern in bypass rule"));
854 } else {
855 panic!("Expected SecurityError");
856 }
857 }
858
859 #[tokio::test]
860 async fn test_oidc_provider_discover_complex_bypass_rules() {
861 let mock_server = MockServer::start().await;
863
864 Mock::given(method("GET"))
866 .and(path("/"))
867 .respond_with(ResponseTemplate::new(200)
868 .set_body_json(serde_json::json!({
869 "issuer": mock_server.uri(),
870 "jwks_uri": format!("{}/jwks", mock_server.uri())
871 })))
872 .mount(&mock_server)
873 .await;
874
875 let config = OidcConfig {
876 issuer_uri: mock_server.uri(),
877 aud: None,
878 shared_secret: None,
879 bypass: vec![
880 RouteRuleConfig {
881 methods: vec!["get".to_string(), "post".to_string()], path: "/api/v*/health".to_string(),
883 },
884 RouteRuleConfig {
885 methods: vec!["*".to_string()],
886 path: "/static/**".to_string(),
887 },
888 ],
889 };
890
891 let result = OidcProvider::discover(config).await;
892 assert!(result.is_ok());
893
894 let provider = result.unwrap();
895 assert_eq!(provider.rules.len(), 2);
896
897 assert!(provider.is_bypassed("GET", "/api/v1/health"));
899 assert!(provider.is_bypassed("POST", "/api/v2/health"));
900 assert!(provider.is_bypassed("DELETE", "/static/css/style.css"));
901 assert!(!provider.is_bypassed("GET", "/api/v1/users"));
902 }
903
904 #[tokio::test]
905 async fn test_jwks_refresh_cache_fresh() {
906 let mock_server = MockServer::start().await;
908
909 Mock::given(method("GET"))
911 .and(path("/"))
912 .respond_with(ResponseTemplate::new(200)
913 .set_body_json(serde_json::json!({
914 "issuer": mock_server.uri(),
915 "jwks_uri": format!("{}/jwks", mock_server.uri())
916 })))
917 .mount(&mock_server)
918 .await;
919
920 let config = OidcConfig {
921 issuer_uri: mock_server.uri(),
922 aud: None,
923 shared_secret: None,
924 bypass: vec![],
925 };
926
927 let provider = OidcProvider::discover(config).await.unwrap();
928
929 {
931 let mut jwks_w = provider.jwks.write().await;
932 *jwks_w = Some(JwkSet { keys: vec![] }); }
934 {
935 let mut refresh_w = provider.last_refresh.write().await;
936 *refresh_w = tokio::time::Instant::now();
937 }
938
939 let result = provider.refresh_jwks().await;
941 assert!(result.is_ok());
942 }
943
944 #[tokio::test]
945 async fn test_jwks_refresh_success() {
946 let mock_server = MockServer::start().await;
948
949 Mock::given(method("GET"))
951 .and(path("/"))
952 .respond_with(ResponseTemplate::new(200)
953 .set_body_json(serde_json::json!({
954 "issuer": mock_server.uri(),
955 "jwks_uri": format!("{}/jwks", mock_server.uri())
956 })))
957 .mount(&mock_server)
958 .await;
959
960 Mock::given(method("GET"))
962 .and(path("/jwks"))
963 .respond_with(ResponseTemplate::new(200)
964 .set_body_json(serde_json::json!({
965 "keys": [
966 {
967 "kty": "RSA",
968 "kid": "test-key-1",
969 "use": "sig",
970 "alg": "RS256",
971 "n": "test-modulus",
972 "e": "AQAB"
973 }
974 ]
975 })))
976 .mount(&mock_server)
977 .await;
978
979 let config = OidcConfig {
980 issuer_uri: mock_server.uri(),
981 aud: None,
982 shared_secret: None,
983 bypass: vec![],
984 };
985
986 let provider = OidcProvider::discover(config).await.unwrap();
987
988 {
990 let mut jwks_w = provider.jwks.write().await;
991 *jwks_w = None; }
993 {
994 let mut refresh_w = provider.last_refresh.write().await;
995 *refresh_w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH + std::time::Duration::from_secs(1))
997 .unwrap_or_else(|| {
998 tokio::time::Instant::now().checked_sub(std::time::Duration::from_millis(1))
1000 .unwrap_or_else(|| tokio::time::Instant::now())
1001 });
1002 }
1003
1004 let result = provider.refresh_jwks().await;
1005 assert!(result.is_ok());
1006
1007 let jwks = provider.jwks.read().await;
1009 assert!(jwks.is_some());
1010 let jwks = jwks.as_ref().unwrap();
1011 assert_eq!(jwks.keys.len(), 1);
1012 assert_eq!(jwks.keys[0].common.key_id, Some("test-key-1".to_string()));
1013 }
1014
1015 #[tokio::test]
1016 async fn test_jwks_refresh_http_error() {
1017 let mock_server = MockServer::start().await;
1019
1020 Mock::given(method("GET"))
1022 .and(path("/"))
1023 .respond_with(ResponseTemplate::new(200)
1024 .set_body_json(serde_json::json!({
1025 "issuer": mock_server.uri(),
1026 "jwks_uri": format!("{}/jwks", mock_server.uri())
1027 })))
1028 .mount(&mock_server)
1029 .await;
1030
1031 Mock::given(method("GET"))
1033 .and(path("/jwks"))
1034 .respond_with(ResponseTemplate::new(500))
1035 .mount(&mock_server)
1036 .await;
1037
1038 let config = OidcConfig {
1039 issuer_uri: mock_server.uri(),
1040 aud: None,
1041 shared_secret: None,
1042 bypass: vec![],
1043 };
1044
1045 let provider = OidcProvider::discover(config).await.unwrap();
1046
1047 {
1049 let mut w = provider.last_refresh.write().await;
1050 *w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
1051 .unwrap_or_else(|| {
1052 tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
1053 .unwrap_or_else(|| tokio::time::Instant::now())
1054 });
1055 }
1056
1057 let result = provider.refresh_jwks().await;
1058 assert!(result.is_err());
1059
1060 if let Err(ProxyError::SecurityError(msg)) = result {
1061 assert!(msg.contains("JWKS endpoint returned error"));
1062 } else {
1063 panic!("Expected SecurityError");
1064 }
1065 }
1066
1067 #[tokio::test]
1068 async fn test_jwks_refresh_invalid_json() {
1069 let mock_server = MockServer::start().await;
1071
1072 Mock::given(method("GET"))
1074 .and(path("/"))
1075 .respond_with(ResponseTemplate::new(200)
1076 .set_body_json(serde_json::json!({
1077 "issuer": mock_server.uri(),
1078 "jwks_uri": format!("{}/jwks", mock_server.uri())
1079 })))
1080 .mount(&mock_server)
1081 .await;
1082
1083 Mock::given(method("GET"))
1085 .and(path("/jwks"))
1086 .respond_with(ResponseTemplate::new(200)
1087 .set_body_string("invalid json"))
1088 .mount(&mock_server)
1089 .await;
1090
1091 let config = OidcConfig {
1092 issuer_uri: mock_server.uri(),
1093 aud: None,
1094 shared_secret: None,
1095 bypass: vec![],
1096 };
1097
1098 let provider = OidcProvider::discover(config).await.unwrap();
1099
1100 {
1102 let mut jwks_w = provider.jwks.write().await;
1103 *jwks_w = None; }
1105 {
1106 let mut refresh_w = provider.last_refresh.write().await;
1107 *refresh_w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH + std::time::Duration::from_secs(1))
1109 .unwrap_or_else(|| {
1110 tokio::time::Instant::now().checked_sub(std::time::Duration::from_millis(1))
1112 .unwrap_or_else(|| tokio::time::Instant::now())
1113 });
1114 }
1115
1116 let result = provider.refresh_jwks().await;
1117 assert!(result.is_err());
1118
1119 if let Err(ProxyError::SecurityError(msg)) = result {
1120 assert!(msg.contains("Failed to parse JWKS response"));
1121 } else {
1122 panic!("Expected SecurityError");
1123 }
1124 }
1125
1126 #[tokio::test]
1127 async fn test_jwks_refresh_connection_error() {
1128 let provider = OidcProvider {
1129 issuer: "https://auth.example.com".to_string(),
1130 aud: None,
1131 shared_secret: None,
1132 jwks_uri: "http://invalid-host-12345.example.com/jwks".to_string(),
1133 jwks: Arc::new(RwLock::new(None)),
1134 last_refresh: Arc::new(RwLock::new(
1135 tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
1136 .unwrap_or_else(|| {
1137 tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
1138 .unwrap_or_else(|| tokio::time::Instant::now())
1139 })
1140 )),
1141 http: reqwest::Client::new(),
1142 rules: vec![],
1143 };
1144
1145 let result = provider.refresh_jwks().await;
1146 assert!(result.is_err());
1147
1148 if let Err(ProxyError::SecurityError(msg)) = result {
1149 assert!(msg.contains("Failed to connect to JWKS endpoint"));
1150 } else {
1151 panic!("Expected SecurityError");
1152 }
1153 }
1154
1155 #[tokio::test]
1156 async fn test_validate_token_invalid_header() {
1157 let provider = OidcProvider {
1158 issuer: "https://auth.example.com".to_string(),
1159 aud: None,
1160 shared_secret: None,
1161 jwks_uri: "https://auth.example.com/jwks".to_string(),
1162 jwks: Arc::new(RwLock::new(None)),
1163 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1164 http: reqwest::Client::new(),
1165 rules: vec![],
1166 };
1167
1168 let result = provider.validate_token("invalid.token.here").await;
1170 assert!(result.is_err());
1171
1172 if let Err(ProxyError::SecurityError(msg)) = result {
1173 assert!(msg.contains("Invalid JWT header"));
1174 } else {
1175 panic!("Expected SecurityError");
1176 }
1177 }
1178
1179 #[tokio::test]
1180 async fn test_validate_token_unsupported_algorithm() {
1181 use jsonwebtoken::Header;
1182 use serde_json::json;
1183
1184 let provider = OidcProvider {
1185 issuer: "https://auth.example.com".to_string(),
1186 aud: None,
1187 shared_secret: None,
1188 jwks_uri: "https://auth.example.com/jwks".to_string(),
1189 jwks: Arc::new(RwLock::new(None)),
1190 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1191 http: reqwest::Client::new(),
1192 rules: vec![],
1193 };
1194
1195 let mut _header = Header::new(jsonwebtoken::Algorithm::HS256);
1199 _header.alg = jsonwebtoken::Algorithm::HS256; let header_json = json!({"alg": "none", "typ": "JWT"});
1204 let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1205 .encode(header_json.to_string().as_bytes());
1206 let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1207 .encode(json!({"sub": "test"}).to_string().as_bytes());
1208 let token = format!("{}.{}.signature", header_b64, payload_b64);
1209
1210 let result = provider.validate_token(&token).await;
1211 assert!(result.is_err());
1212
1213 if let Err(ProxyError::SecurityError(msg)) = result {
1214 assert!(msg.contains("Invalid JWT header") || msg.contains("Algorithm not allowed"));
1215 } else {
1216 panic!("Expected SecurityError");
1217 }
1218 }
1219
1220 #[tokio::test]
1221 async fn test_validate_token_no_jwks_available() {
1222 use jsonwebtoken::{Header, Algorithm};
1223 use serde_json::json;
1224
1225 let provider = OidcProvider {
1226 issuer: "https://auth.example.com".to_string(),
1227 aud: None,
1228 shared_secret: None,
1229 jwks_uri: "https://auth.example.com/jwks".to_string(),
1230 jwks: Arc::new(RwLock::new(None)), last_refresh: Arc::new(RwLock::new(
1232 tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
1233 .unwrap_or_else(|| {
1234 tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
1235 .unwrap_or_else(|| tokio::time::Instant::now())
1236 })
1237 )),
1238 http: reqwest::Client::new(),
1239 rules: vec![],
1240 };
1241
1242 let _header = Header {
1245 alg: Algorithm::RS256,
1246 kid: Some("test-key".to_string()),
1247 ..Default::default()
1248 };
1249
1250 let header_json = json!({
1251 "alg": "RS256",
1252 "typ": "JWT",
1253 "kid": "test-key"
1254 });
1255 let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1256 .encode(header_json.to_string().as_bytes());
1257 let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1258 .encode(json!({"sub": "test"}).to_string().as_bytes());
1259 let token = format!("{}.{}.signature", header_b64, payload_b64);
1260
1261 let result = provider.validate_token(&token).await;
1262 assert!(result.is_err());
1263
1264 if let Err(ProxyError::SecurityError(msg)) = result {
1265 assert!(msg.contains("No JWKS available") || msg.contains("Failed to connect"));
1266 } else {
1267 panic!("Expected SecurityError");
1268 }
1269 }
1270
1271 #[tokio::test]
1272 async fn test_validate_token_hmac_success() {
1273 let provider = OidcProvider {
1274 issuer: "https://auth.example.com".to_string(),
1275 aud: Some("test-audience".to_string()),
1276 shared_secret: Some("test-secret-key".to_string()),
1277 jwks_uri: "https://auth.example.com/jwks".to_string(),
1278 jwks: Arc::new(RwLock::new(None)),
1279 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1280 http: reqwest::Client::new(),
1281 rules: vec![],
1282 };
1283
1284 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1286
1287 let header = Header::new(Algorithm::HS256);
1288
1289 #[derive(serde::Serialize)]
1290 struct HmacTestClaims {
1291 iss: String,
1292 aud: String,
1293 sub: String,
1294 exp: i64,
1295 iat: i64,
1296 }
1297
1298 let claims = HmacTestClaims {
1299 iss: "https://auth.example.com".to_string(),
1300 aud: "test-audience".to_string(),
1301 sub: "test-user".to_string(),
1302 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1303 iat: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64,
1304 };
1305
1306 let token = encode(
1307 &header,
1308 &claims,
1309 &EncodingKey::from_secret("test-secret-key".as_ref())
1310 ).unwrap();
1311
1312 let result = provider.validate_token(&token).await;
1313 assert!(result.is_ok());
1314
1315 let validated_claims = result.unwrap();
1316 assert_eq!(validated_claims["iss"], "https://auth.example.com");
1317 assert_eq!(validated_claims["aud"], "test-audience");
1318 assert_eq!(validated_claims["sub"], "test-user");
1319 }
1320
1321 #[tokio::test]
1322 async fn test_validate_token_hmac_wrong_secret() {
1323 let provider = OidcProvider {
1324 issuer: "https://auth.example.com".to_string(),
1325 aud: None,
1326 shared_secret: Some("wrong-secret".to_string()),
1327 jwks_uri: "https://auth.example.com/jwks".to_string(),
1328 jwks: Arc::new(RwLock::new(None)),
1329 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1330 http: reqwest::Client::new(),
1331 rules: vec![],
1332 };
1333
1334 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1336
1337 let header = Header::new(Algorithm::HS256);
1338
1339 #[derive(serde::Serialize)]
1340 struct WrongSecretClaims {
1341 iss: String,
1342 sub: String,
1343 exp: i64,
1344 }
1345
1346 let claims = WrongSecretClaims {
1347 iss: "https://auth.example.com".to_string(),
1348 sub: "test-user".to_string(),
1349 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1350 };
1351
1352 let token = encode(
1353 &header,
1354 &claims,
1355 &EncodingKey::from_secret("correct-secret".as_ref())
1356 ).unwrap();
1357
1358 let result = provider.validate_token(&token).await;
1359 assert!(result.is_err());
1360
1361 if let Err(ProxyError::SecurityError(msg)) = result {
1362 assert!(msg.contains("JWT validation failed: InvalidSignature"));
1363 } else {
1364 panic!("Expected SecurityError");
1365 }
1366 }
1367
1368 #[tokio::test]
1369 async fn test_validate_token_hmac_no_shared_secret() {
1370 let provider = OidcProvider {
1371 issuer: "https://auth.example.com".to_string(),
1372 aud: None,
1373 shared_secret: None, jwks_uri: "https://auth.example.com/jwks".to_string(),
1375 jwks: Arc::new(RwLock::new(None)),
1376 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1377 http: reqwest::Client::new(),
1378 rules: vec![],
1379 };
1380
1381 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1383
1384 let header = Header::new(Algorithm::HS256);
1385
1386 #[derive(serde::Serialize)]
1387 struct NoSecretClaims {
1388 iss: String,
1389 sub: String,
1390 exp: i64,
1391 }
1392
1393 let claims = NoSecretClaims {
1394 iss: "https://auth.example.com".to_string(),
1395 sub: "test-user".to_string(),
1396 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1397 };
1398
1399 let token = encode(
1400 &header,
1401 &claims,
1402 &EncodingKey::from_secret("test-secret".as_ref())
1403 ).unwrap();
1404
1405 let result = provider.validate_token(&token).await;
1406 assert!(result.is_err());
1407
1408 if let Err(ProxyError::SecurityError(msg)) = result {
1409 assert!(msg.contains("No key ID in token and no shared secret configured"));
1410 } else {
1411 panic!("Expected SecurityError");
1412 }
1413 }
1414
1415 #[tokio::test]
1416 async fn test_validate_claims_wrong_issuer() {
1417 let provider = OidcProvider {
1418 issuer: "https://auth.example.com".to_string(),
1419 aud: None,
1420 shared_secret: Some("test-secret".to_string()),
1421 jwks_uri: "https://auth.example.com/jwks".to_string(),
1422 jwks: Arc::new(RwLock::new(None)),
1423 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1424 http: reqwest::Client::new(),
1425 rules: vec![],
1426 };
1427
1428 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1430
1431 let header = Header::new(Algorithm::HS256);
1432
1433 #[derive(serde::Serialize)]
1434 struct WrongIssuerClaims {
1435 iss: String,
1436 sub: String,
1437 exp: i64,
1438 }
1439
1440 let claims = WrongIssuerClaims {
1441 iss: "https://wrong-issuer.com".to_string(), sub: "test-user".to_string(),
1443 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1444 };
1445
1446 let token = encode(
1447 &header,
1448 &claims,
1449 &EncodingKey::from_secret("test-secret".as_ref())
1450 ).unwrap();
1451
1452 let result = provider.validate_token(&token).await;
1453 assert!(result.is_err());
1454
1455 if let Err(ProxyError::SecurityError(msg)) = result {
1456 assert!(msg.contains("InvalidIssuer"));
1457 } else {
1458 panic!("Expected SecurityError");
1459 }
1460 }
1461
1462 #[tokio::test]
1463 async fn test_validate_claims_wrong_audience() {
1464 let provider = OidcProvider {
1465 issuer: "https://auth.example.com".to_string(),
1466 aud: Some("expected-audience".to_string()),
1467 shared_secret: Some("test-secret".to_string()),
1468 jwks_uri: "https://auth.example.com/jwks".to_string(),
1469 jwks: Arc::new(RwLock::new(None)),
1470 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1471 http: reqwest::Client::new(),
1472 rules: vec![],
1473 };
1474
1475 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1477
1478 let header = Header::new(Algorithm::HS256);
1479
1480 #[derive(serde::Serialize)]
1481 struct WrongAudClaims {
1482 iss: String,
1483 aud: String,
1484 sub: String,
1485 exp: i64,
1486 }
1487
1488 let claims = WrongAudClaims {
1489 iss: "https://auth.example.com".to_string(),
1490 aud: "wrong-audience".to_string(), sub: "test-user".to_string(),
1492 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1493 };
1494
1495 let token = encode(
1496 &header,
1497 &claims,
1498 &EncodingKey::from_secret("test-secret".as_ref())
1499 ).unwrap();
1500
1501 let result = provider.validate_token(&token).await;
1502 assert!(result.is_err());
1503
1504 if let Err(ProxyError::SecurityError(msg)) = result {
1505 assert!(msg.contains("InvalidAudience"));
1506 } else {
1507 panic!("Expected SecurityError");
1508 }
1509 }
1510
1511 #[tokio::test]
1512 async fn test_validate_claims_expired_token() {
1513 let provider = OidcProvider {
1514 issuer: "https://auth.example.com".to_string(),
1515 aud: None,
1516 shared_secret: Some("test-secret".to_string()),
1517 jwks_uri: "https://auth.example.com/jwks".to_string(),
1518 jwks: Arc::new(RwLock::new(None)),
1519 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1520 http: reqwest::Client::new(),
1521 rules: vec![],
1522 };
1523
1524 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1526
1527 let header = Header::new(Algorithm::HS256);
1528
1529 #[derive(serde::Serialize)]
1530 struct ExpiredClaims {
1531 iss: String,
1532 sub: String,
1533 exp: i64,
1534 }
1535
1536 let claims = ExpiredClaims {
1537 iss: "https://auth.example.com".to_string(),
1538 sub: "test-user".to_string(),
1539 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - 3600) as i64, };
1541
1542 let token = encode(
1543 &header,
1544 &claims,
1545 &EncodingKey::from_secret("test-secret".as_ref())
1546 ).unwrap();
1547
1548 let result = provider.validate_token(&token).await;
1549 assert!(result.is_err());
1550
1551 if let Err(ProxyError::SecurityError(msg)) = result {
1552 assert!(msg.contains("ExpiredSignature"));
1553 } else {
1554 panic!("Expected SecurityError");
1555 }
1556 }
1557
1558 #[tokio::test]
1559 async fn test_validate_claims_long_expiration() {
1560 let provider = OidcProvider {
1561 issuer: "https://auth.example.com".to_string(),
1562 aud: None,
1563 shared_secret: Some("test-secret".to_string()),
1564 jwks_uri: "https://auth.example.com/jwks".to_string(),
1565 jwks: Arc::new(RwLock::new(None)),
1566 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1567 http: reqwest::Client::new(),
1568 rules: vec![],
1569 };
1570
1571 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1573
1574
1575 let header = Header::new(Algorithm::HS256);
1576
1577 #[derive(serde::Serialize)]
1579 struct TestClaimsLongExp {
1580 iss: String,
1581 sub: String,
1582 exp: i64,
1583 }
1584
1585 let claims = TestClaimsLongExp {
1586 iss: "https://auth.example.com".to_string(),
1587 sub: "test-user".to_string(),
1588 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + (100 * 365 * 24 * 3600)) as i64, };
1590
1591 let token = encode(
1592 &header,
1593 &claims,
1594 &EncodingKey::from_secret("test-secret".as_ref())
1595 ).unwrap();
1596
1597 let result = provider.validate_token(&token).await;
1598 if result.is_err() {
1599 println!("Long expiration test error: {:?}", result);
1600 }
1601 assert!(result.is_ok()); let validated_claims = result.unwrap();
1604 assert_eq!(validated_claims["iss"], "https://auth.example.com");
1605 assert_eq!(validated_claims["sub"], "test-user");
1606 }
1607
1608 #[tokio::test]
1609 async fn test_validate_token_missing_key_id() {
1610 let mock_server = MockServer::start().await;
1612
1613 Mock::given(method("GET"))
1615 .and(path("/"))
1616 .respond_with(ResponseTemplate::new(200)
1617 .set_body_json(serde_json::json!({
1618 "issuer": mock_server.uri(),
1619 "jwks_uri": format!("{}/jwks", mock_server.uri())
1620 })))
1621 .mount(&mock_server)
1622 .await;
1623
1624 Mock::given(method("GET"))
1626 .and(path("/jwks"))
1627 .respond_with(ResponseTemplate::new(200)
1628 .set_body_json(serde_json::json!({
1629 "keys": [
1630 {
1631 "kty": "RSA",
1632 "kid": "test-key-1",
1633 "use": "sig",
1634 "alg": "RS256",
1635 "n": "test-modulus",
1636 "e": "AQAB"
1637 }
1638 ]
1639 })))
1640 .mount(&mock_server)
1641 .await;
1642
1643 let config = OidcConfig {
1644 issuer_uri: mock_server.uri(),
1645 aud: None,
1646 shared_secret: None,
1647 bypass: vec![],
1648 };
1649
1650 let provider = OidcProvider::discover(config).await.unwrap();
1651
1652 use serde_json::json;
1654 let header_json = json!({
1655 "alg": "RS256",
1656 "typ": "JWT"
1657 });
1659 let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1660 .encode(header_json.to_string().as_bytes());
1661 let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1662 .encode(json!({"sub": "test"}).to_string().as_bytes());
1663 let token = format!("{}.{}.signature", header_b64, payload_b64);
1664
1665 let result = provider.validate_token(&token).await;
1666 assert!(result.is_err());
1667
1668 if let Err(ProxyError::SecurityError(msg)) = result {
1669 assert!(msg.contains("No key ID in token and no shared secret configured"));
1670 } else {
1671 panic!("Expected SecurityError");
1672 }
1673 }
1674
1675 #[tokio::test]
1676 async fn test_validate_token_key_not_found() {
1677 let mock_server = MockServer::start().await;
1679
1680 Mock::given(method("GET"))
1682 .and(path("/"))
1683 .respond_with(ResponseTemplate::new(200)
1684 .set_body_json(serde_json::json!({
1685 "issuer": mock_server.uri(),
1686 "jwks_uri": format!("{}/jwks", mock_server.uri())
1687 })))
1688 .mount(&mock_server)
1689 .await;
1690
1691 Mock::given(method("GET"))
1693 .and(path("/jwks"))
1694 .respond_with(ResponseTemplate::new(200)
1695 .set_body_json(serde_json::json!({
1696 "keys": [
1697 {
1698 "kty": "RSA",
1699 "kid": "different-key",
1700 "use": "sig",
1701 "alg": "RS256",
1702 "n": "test-modulus",
1703 "e": "AQAB"
1704 }
1705 ]
1706 })))
1707 .mount(&mock_server)
1708 .await;
1709
1710 let config = OidcConfig {
1711 issuer_uri: mock_server.uri(),
1712 aud: None,
1713 shared_secret: None,
1714 bypass: vec![],
1715 };
1716
1717 let provider = OidcProvider::discover(config).await.unwrap();
1718
1719 use serde_json::json;
1721 let header_json = json!({
1722 "alg": "RS256",
1723 "typ": "JWT",
1724 "kid": "missing-key"
1725 });
1726 let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1727 .encode(header_json.to_string().as_bytes());
1728 let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1729 .encode(json!({"sub": "test"}).to_string().as_bytes());
1730 let token = format!("{}.{}.signature", header_b64, payload_b64);
1731
1732 let result = provider.validate_token(&token).await;
1733 assert!(result.is_err());
1734
1735 if let Err(ProxyError::SecurityError(msg)) = result {
1736 assert!(msg.contains("not found in JWKS"));
1737 } else {
1738 panic!("Expected SecurityError");
1739 }
1740 }
1741
1742 #[tokio::test]
1743 async fn test_integration_full_oidc_flow_with_bypass() {
1744 let mock_server = MockServer::start().await;
1746
1747 Mock::given(method("GET"))
1749 .and(path("/"))
1750 .respond_with(ResponseTemplate::new(200)
1751 .set_body_json(serde_json::json!({
1752 "issuer": mock_server.uri(),
1753 "jwks_uri": format!("{}/jwks", mock_server.uri())
1754 })))
1755 .mount(&mock_server)
1756 .await;
1757
1758 let config = OidcConfig {
1759 issuer_uri: mock_server.uri(),
1760 aud: Some("test-app".to_string()),
1761 shared_secret: Some("integration-secret".to_string()),
1762 bypass: vec![
1763 RouteRuleConfig {
1764 methods: vec!["GET".to_string()],
1765 path: "/health".to_string(),
1766 },
1767 RouteRuleConfig {
1768 methods: vec!["*".to_string()],
1769 path: "/public/*".to_string(),
1770 },
1771 ],
1772 };
1773
1774 let provider = OidcProvider::discover(config).await.unwrap();
1775
1776 let bypass_request = ProxyRequest {
1778 method: HttpMethod::Get,
1779 path: "/health".to_string(),
1780 query: None,
1781 headers: HeaderMap::new(),
1782 body: reqwest::Body::from(Vec::new()),
1783 context: Arc::new(RwLock::new(RequestContext::default())),
1784 custom_target: Some("http://test.example.com".to_string()),
1785 };
1786
1787 let result = provider.pre(bypass_request).await;
1788 assert!(result.is_ok());
1789
1790 let auth_request = ProxyRequest {
1792 method: HttpMethod::Post,
1793 path: "/api/users".to_string(),
1794 query: None,
1795 headers: HeaderMap::new(), body: reqwest::Body::from(Vec::new()),
1797 context: Arc::new(RwLock::new(RequestContext::default())),
1798 custom_target: Some("http://test.example.com".to_string()),
1799 };
1800
1801 let result = provider.pre(auth_request).await;
1802 assert!(result.is_err());
1803
1804 if let Err(ProxyError::SecurityError(msg)) = result {
1805 assert!(msg.contains("Missing authorization header"));
1806 } else {
1807 panic!("Expected SecurityError for missing auth header");
1808 }
1809 }
1810
1811 #[tokio::test]
1812 async fn test_integration_full_oidc_flow_auth_failure() {
1813 let mock_server = MockServer::start().await;
1815
1816 Mock::given(method("GET"))
1818 .and(path("/"))
1819 .respond_with(ResponseTemplate::new(200)
1820 .set_body_json(serde_json::json!({
1821 "issuer": mock_server.uri(),
1822 "jwks_uri": format!("{}/jwks", mock_server.uri())
1823 })))
1824 .mount(&mock_server)
1825 .await;
1826
1827 let config = OidcConfig {
1828 issuer_uri: mock_server.uri(),
1829 aud: None,
1830 shared_secret: Some("test-secret".to_string()),
1831 bypass: vec![],
1832 };
1833
1834 let provider = OidcProvider::discover(config).await.unwrap();
1835
1836 let request = ProxyRequest {
1838 method: HttpMethod::Post,
1839 path: "/api/users".to_string(),
1840 query: None,
1841 headers: HeaderMap::new(),
1842 body: reqwest::Body::from(Vec::new()),
1843 context: Arc::new(RwLock::new(RequestContext::default())),
1844 custom_target: Some("http://test.example.com".to_string()),
1845 };
1846
1847 let result = provider.pre(request).await;
1848 assert!(result.is_err());
1849
1850 if let Err(ProxyError::SecurityError(msg)) = result {
1851 assert!(msg.contains("Missing authorization header"));
1852 } else {
1853 panic!("Expected SecurityError");
1854 }
1855 }
1856
1857 #[test]
1858 fn test_oidc_provider_is_bypassed() {
1859 let mut builder1 = GlobSetBuilder::new();
1860 builder1.add(Glob::new("/health").unwrap());
1861 let paths1 = builder1.build().unwrap();
1862
1863 let mut builder2 = GlobSetBuilder::new();
1864 builder2.add(Glob::new("/public/*").unwrap());
1865 let paths2 = builder2.build().unwrap();
1866
1867 let rules = vec![
1868 RouteRule {
1869 methods: vec!["GET".to_string()],
1870 paths: paths1,
1871 },
1872 RouteRule {
1873 methods: vec!["*".to_string()],
1874 paths: paths2,
1875 },
1876 ];
1877
1878 let provider = OidcProvider {
1879 issuer: "https://auth.example.com".to_string(),
1880 aud: None,
1881 shared_secret: None,
1882 jwks_uri: "https://auth.example.com/jwks".to_string(),
1883 jwks: Arc::new(RwLock::new(None)),
1884 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1885 http: reqwest::Client::new(),
1886 rules,
1887 };
1888
1889 assert!(provider.is_bypassed("GET", "/health"));
1891 assert!(!provider.is_bypassed("POST", "/health"));
1892 assert!(provider.is_bypassed("GET", "/public/api"));
1893 assert!(provider.is_bypassed("POST", "/public/api"));
1894 assert!(!provider.is_bypassed("GET", "/private/api"));
1895 }
1896
1897 #[test]
1898 fn test_security_provider_trait_implementation() {
1899 let provider = OidcProvider {
1900 issuer: "https://auth.example.com".to_string(),
1901 aud: None,
1902 shared_secret: None,
1903 jwks_uri: "https://auth.example.com/jwks".to_string(),
1904 jwks: Arc::new(RwLock::new(None)),
1905 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1906 http: reqwest::Client::new(),
1907 rules: vec![],
1908 };
1909
1910 assert_eq!(provider.name(), "OidcProvider");
1911 assert_eq!(provider.stage(), SecurityStage::Pre);
1912 }
1913
1914 #[tokio::test]
1915 async fn test_oidc_provider_pre_bypass() {
1916 let mut builder = GlobSetBuilder::new();
1917 builder.add(Glob::new("/health").unwrap());
1918 let paths = builder.build().unwrap();
1919
1920 let rules = vec![
1921 RouteRule {
1922 methods: vec!["GET".to_string()],
1923 paths,
1924 },
1925 ];
1926
1927 let provider = OidcProvider {
1928 issuer: "https://auth.example.com".to_string(),
1929 aud: None,
1930 shared_secret: None,
1931 jwks_uri: "https://auth.example.com/jwks".to_string(),
1932 jwks: Arc::new(RwLock::new(None)),
1933 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1934 http: reqwest::Client::new(),
1935 rules,
1936 };
1937
1938 let headers = HeaderMap::new();
1939 let request = ProxyRequest {
1940 method: HttpMethod::Get,
1941 path: "/health".to_string(),
1942 query: None,
1943 headers,
1944 body: reqwest::Body::from(""),
1945 context: Arc::new(RwLock::new(RequestContext::default())),
1946 custom_target: None,
1947 };
1948
1949 let result = provider.pre(request).await;
1950 assert!(result.is_ok());
1951 }
1952
1953 #[tokio::test]
1954 async fn test_oidc_provider_pre_missing_auth_header() {
1955 let provider = OidcProvider {
1956 issuer: "https://auth.example.com".to_string(),
1957 aud: None,
1958 shared_secret: None,
1959 jwks_uri: "https://auth.example.com/jwks".to_string(),
1960 jwks: Arc::new(RwLock::new(None)),
1961 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1962 http: reqwest::Client::new(),
1963 rules: vec![],
1964 };
1965
1966 let headers = HeaderMap::new();
1967 let request = ProxyRequest {
1968 method: HttpMethod::Get,
1969 path: "/api/users".to_string(),
1970 query: None,
1971 headers,
1972 body: reqwest::Body::from(""),
1973 context: Arc::new(RwLock::new(RequestContext::default())),
1974 custom_target: None,
1975 };
1976
1977 let result = provider.pre(request).await;
1978 assert!(result.is_err());
1979
1980 if let Err(ProxyError::SecurityError(msg)) = result {
1981 assert_eq!(msg, "Missing authorization header");
1982 } else {
1983 panic!("Expected SecurityError");
1984 }
1985 }
1986
1987 #[tokio::test]
1988 async fn test_oidc_provider_pre_invalid_auth_scheme() {
1989 let provider = OidcProvider {
1990 issuer: "https://auth.example.com".to_string(),
1991 aud: None,
1992 shared_secret: None,
1993 jwks_uri: "https://auth.example.com/jwks".to_string(),
1994 jwks: Arc::new(RwLock::new(None)),
1995 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1996 http: reqwest::Client::new(),
1997 rules: vec![],
1998 };
1999
2000 let mut headers = HeaderMap::new();
2001 headers.insert("authorization", "Basic dXNlcjpwYXNz".parse().unwrap());
2002
2003 let request = ProxyRequest {
2004 method: HttpMethod::Get,
2005 path: "/api/users".to_string(),
2006 query: None,
2007 headers,
2008 body: reqwest::Body::from(""),
2009 context: Arc::new(RwLock::new(RequestContext::default())),
2010 custom_target: None,
2011 };
2012
2013 let result = provider.pre(request).await;
2014 assert!(result.is_err());
2015
2016 if let Err(ProxyError::SecurityError(msg)) = result {
2017 assert!(msg.contains("Invalid authorization scheme"));
2018 assert!(msg.contains("expected 'Bearer'"));
2019 } else {
2020 panic!("Expected SecurityError");
2021 }
2022 }
2023
2024 #[tokio::test]
2025 async fn test_oidc_provider_pre_empty_bearer_token() {
2026 let provider = OidcProvider {
2027 issuer: "https://auth.example.com".to_string(),
2028 aud: None,
2029 shared_secret: None,
2030 jwks_uri: "https://auth.example.com/jwks".to_string(),
2031 jwks: Arc::new(RwLock::new(None)),
2032 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2033 http: reqwest::Client::new(),
2034 rules: vec![],
2035 };
2036
2037 let mut headers = HeaderMap::new();
2038 headers.insert("authorization", "Bearer ".parse().unwrap());
2039
2040 let request = ProxyRequest {
2041 method: HttpMethod::Get,
2042 path: "/api/users".to_string(),
2043 query: None,
2044 headers,
2045 body: reqwest::Body::from(""),
2046 context: Arc::new(RwLock::new(RequestContext::default())),
2047 custom_target: None,
2048 };
2049
2050 let result = provider.pre(request).await;
2051 assert!(result.is_err());
2052
2053 if let Err(ProxyError::SecurityError(msg)) = result {
2054 assert_eq!(msg, "Empty bearer token");
2055 } else {
2056 panic!("Expected SecurityError");
2057 }
2058 }
2059
2060 #[tokio::test]
2061 async fn test_jwk_to_decoding_key_rsa_success() {
2062 let mock_server = MockServer::start().await;
2064
2065 Mock::given(method("GET"))
2066 .and(path("/"))
2067 .respond_with(ResponseTemplate::new(200)
2068 .set_body_json(serde_json::json!({
2069 "issuer": mock_server.uri(),
2070 "jwks_uri": format!("{}/jwks", mock_server.uri())
2071 })))
2072 .mount(&mock_server)
2073 .await;
2074
2075 Mock::given(method("GET"))
2077 .and(path("/jwks"))
2078 .respond_with(ResponseTemplate::new(200)
2079 .set_body_json(serde_json::json!({
2080 "keys": [
2081 {
2082 "kty": "RSA",
2083 "kid": "rsa-key-1",
2084 "use": "sig",
2085 "alg": "RS256",
2086 "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
2087 "e": "AQAB"
2088 }
2089 ]
2090 })))
2091 .mount(&mock_server)
2092 .await;
2093
2094 let config = OidcConfig {
2095 issuer_uri: mock_server.uri(),
2096 aud: None,
2097 shared_secret: None,
2098 bypass: vec![],
2099 };
2100
2101 let provider = OidcProvider::discover(config).await.unwrap();
2102
2103 provider.refresh_jwks().await.unwrap();
2105
2106 let jwks = provider.jwks.read().await;
2107 let jwks = jwks.as_ref().unwrap();
2108 let jwk = &jwks.keys[0];
2109
2110 let result = provider.jwk_to_decoding_key(jwk);
2111 assert!(result.is_ok());
2112 }
2113
2114 #[tokio::test]
2115 async fn test_jwk_to_decoding_key_ec_success() {
2116 let mock_server = MockServer::start().await;
2118
2119 Mock::given(method("GET"))
2120 .and(path("/"))
2121 .respond_with(ResponseTemplate::new(200)
2122 .set_body_json(serde_json::json!({
2123 "issuer": mock_server.uri(),
2124 "jwks_uri": format!("{}/jwks", mock_server.uri())
2125 })))
2126 .mount(&mock_server)
2127 .await;
2128
2129 Mock::given(method("GET"))
2131 .and(path("/jwks"))
2132 .respond_with(ResponseTemplate::new(200)
2133 .set_body_json(serde_json::json!({
2134 "keys": [
2135 {
2136 "kty": "EC",
2137 "kid": "ec-key-1",
2138 "use": "sig",
2139 "alg": "ES256",
2140 "crv": "P-256",
2141 "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
2142 "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
2143 }
2144 ]
2145 })))
2146 .mount(&mock_server)
2147 .await;
2148
2149 let config = OidcConfig {
2150 issuer_uri: mock_server.uri(),
2151 aud: None,
2152 shared_secret: None,
2153 bypass: vec![],
2154 };
2155
2156 let provider = OidcProvider::discover(config).await.unwrap();
2157
2158 provider.refresh_jwks().await.unwrap();
2160
2161 let jwks = provider.jwks.read().await;
2162 let jwks = jwks.as_ref().unwrap();
2163 let jwk = &jwks.keys[0];
2164
2165 let result = provider.jwk_to_decoding_key(jwk);
2166 assert!(result.is_ok());
2167 }
2168
2169 #[tokio::test]
2170 async fn test_jwk_to_decoding_key_octet_key_success() {
2171 let mock_server = MockServer::start().await;
2173
2174 Mock::given(method("GET"))
2175 .and(path("/"))
2176 .respond_with(ResponseTemplate::new(200)
2177 .set_body_json(serde_json::json!({
2178 "issuer": mock_server.uri(),
2179 "jwks_uri": format!("{}/jwks", mock_server.uri())
2180 })))
2181 .mount(&mock_server)
2182 .await;
2183
2184 Mock::given(method("GET"))
2186 .and(path("/jwks"))
2187 .respond_with(ResponseTemplate::new(200)
2188 .set_body_json(serde_json::json!({
2189 "keys": [
2190 {
2191 "kty": "oct",
2192 "kid": "hmac-key-1",
2193 "use": "sig",
2194 "alg": "HS256",
2195 "k": "GawgguFyGrWKav7AX4VKUg"
2196 }
2197 ]
2198 })))
2199 .mount(&mock_server)
2200 .await;
2201
2202 let config = OidcConfig {
2203 issuer_uri: mock_server.uri(),
2204 aud: None,
2205 shared_secret: None,
2206 bypass: vec![],
2207 };
2208
2209 let provider = OidcProvider::discover(config).await.unwrap();
2210
2211 provider.refresh_jwks().await.unwrap();
2213
2214 let jwks = provider.jwks.read().await;
2215 let jwks = jwks.as_ref().unwrap();
2216 let jwk = &jwks.keys[0];
2217
2218 let result = provider.jwk_to_decoding_key(jwk);
2219 assert!(result.is_ok());
2220 }
2221
2222 #[tokio::test]
2223 async fn test_jwk_to_decoding_key_okp_success() {
2224 let mock_server = MockServer::start().await;
2226
2227 Mock::given(method("GET"))
2228 .and(path("/"))
2229 .respond_with(ResponseTemplate::new(200)
2230 .set_body_json(serde_json::json!({
2231 "issuer": mock_server.uri(),
2232 "jwks_uri": format!("{}/jwks", mock_server.uri())
2233 })))
2234 .mount(&mock_server)
2235 .await;
2236
2237 Mock::given(method("GET"))
2239 .and(path("/jwks"))
2240 .respond_with(ResponseTemplate::new(200)
2241 .set_body_json(serde_json::json!({
2242 "keys": [
2243 {
2244 "kty": "OKP",
2245 "kid": "ed25519-key-1",
2246 "use": "sig",
2247 "alg": "EdDSA",
2248 "crv": "Ed25519",
2249 "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
2250 }
2251 ]
2252 })))
2253 .mount(&mock_server)
2254 .await;
2255
2256 let config = OidcConfig {
2257 issuer_uri: mock_server.uri(),
2258 aud: None,
2259 shared_secret: None,
2260 bypass: vec![],
2261 };
2262
2263 let provider = OidcProvider::discover(config).await.unwrap();
2264
2265 provider.refresh_jwks().await.unwrap();
2267
2268 let jwks = provider.jwks.read().await;
2269 let jwks = jwks.as_ref().unwrap();
2270 let jwk = &jwks.keys[0];
2271
2272 let result = provider.jwk_to_decoding_key(jwk);
2273 assert!(result.is_ok());
2274 }
2275
2276 #[tokio::test]
2277 async fn test_validate_token_with_kid_fallback_to_shared_secret() {
2278 let provider = OidcProvider {
2279 issuer: "https://auth.example.com".to_string(),
2280 aud: None,
2281 shared_secret: Some("test-secret".to_string()),
2282 jwks_uri: "https://auth.example.com/jwks".to_string(),
2283 jwks: Arc::new(RwLock::new(Some(JwkSet { keys: vec![] }))), last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2285 http: reqwest::Client::new(),
2286 rules: vec![],
2287 };
2288
2289 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
2291
2292 let mut header = Header::new(Algorithm::HS256);
2293 header.kid = Some("missing-key".to_string());
2294
2295 #[derive(serde::Serialize)]
2296 struct TestClaims {
2297 iss: String,
2298 sub: String,
2299 exp: i64,
2300 }
2301
2302 let claims = TestClaims {
2303 iss: "https://auth.example.com".to_string(),
2304 sub: "test-user".to_string(),
2305 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
2306 };
2307
2308 let token = encode(
2309 &header,
2310 &claims,
2311 &EncodingKey::from_secret("test-secret".as_ref())
2312 ).unwrap();
2313
2314 let result = provider.validate_token(&token).await;
2315 assert!(result.is_ok());
2316
2317 let validated_claims = result.unwrap();
2318 assert_eq!(validated_claims["iss"], "https://auth.example.com");
2319 assert_eq!(validated_claims["sub"], "test-user");
2320 }
2321
2322 #[tokio::test]
2323 async fn test_validate_token_non_hmac_algorithm_with_missing_kid() {
2324 let provider = OidcProvider {
2325 issuer: "https://auth.example.com".to_string(),
2326 aud: None,
2327 shared_secret: Some("test-secret".to_string()),
2328 jwks_uri: "https://auth.example.com/jwks".to_string(),
2329 jwks: Arc::new(RwLock::new(Some(JwkSet { keys: vec![] }))), last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2331 http: reqwest::Client::new(),
2332 rules: vec![],
2333 };
2334
2335 use serde_json::json;
2337 let header_json = json!({
2338 "alg": "RS256",
2339 "typ": "JWT",
2340 "kid": "missing-rsa-key"
2341 });
2342 let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
2343 .encode(header_json.to_string().as_bytes());
2344 let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
2345 .encode(json!({"sub": "test"}).to_string().as_bytes());
2346 let token = format!("{}.{}.signature", header_b64, payload_b64);
2347
2348 let result = provider.validate_token(&token).await;
2349 assert!(result.is_err());
2350
2351 if let Err(ProxyError::SecurityError(msg)) = result {
2352 assert!(msg.contains("not found in JWKS"));
2353 } else {
2354 panic!("Expected SecurityError");
2355 }
2356 }
2357
2358 #[tokio::test]
2359 async fn test_validate_token_different_algorithms() {
2360 let provider = OidcProvider {
2361 issuer: "https://auth.example.com".to_string(),
2362 aud: None,
2363 shared_secret: Some("test-secret-key-for-hs384".to_string()),
2364 jwks_uri: "https://auth.example.com/jwks".to_string(),
2365 jwks: Arc::new(RwLock::new(None)),
2366 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2367 http: reqwest::Client::new(),
2368 rules: vec![],
2369 };
2370
2371 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
2373
2374 let header = Header::new(Algorithm::HS384);
2375
2376 #[derive(serde::Serialize)]
2377 struct TestClaims {
2378 iss: String,
2379 sub: String,
2380 exp: i64,
2381 }
2382
2383 let claims = TestClaims {
2384 iss: "https://auth.example.com".to_string(),
2385 sub: "test-user".to_string(),
2386 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
2387 };
2388
2389 let token = encode(
2390 &header,
2391 &claims,
2392 &EncodingKey::from_secret("test-secret-key-for-hs384".as_ref())
2393 ).unwrap();
2394
2395 let result = provider.validate_token(&token).await;
2396 assert!(result.is_ok());
2397
2398 let validated_claims = result.unwrap();
2399 assert_eq!(validated_claims["iss"], "https://auth.example.com");
2400 assert_eq!(validated_claims["sub"], "test-user");
2401 }
2402
2403 #[tokio::test]
2404 async fn test_validate_token_hs512_algorithm() {
2405 let provider = OidcProvider {
2406 issuer: "https://auth.example.com".to_string(),
2407 aud: None,
2408 shared_secret: Some("test-secret-key-for-hs512-algorithm".to_string()),
2409 jwks_uri: "https://auth.example.com/jwks".to_string(),
2410 jwks: Arc::new(RwLock::new(None)),
2411 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2412 http: reqwest::Client::new(),
2413 rules: vec![],
2414 };
2415
2416 use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
2418
2419 let header = Header::new(Algorithm::HS512);
2420
2421 #[derive(serde::Serialize)]
2422 struct TestClaims {
2423 iss: String,
2424 sub: String,
2425 exp: i64,
2426 }
2427
2428 let claims = TestClaims {
2429 iss: "https://auth.example.com".to_string(),
2430 sub: "test-user".to_string(),
2431 exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
2432 };
2433
2434 let token = encode(
2435 &header,
2436 &claims,
2437 &EncodingKey::from_secret("test-secret-key-for-hs512-algorithm".as_ref())
2438 ).unwrap();
2439
2440 let result = provider.validate_token(&token).await;
2441 assert!(result.is_ok());
2442
2443 let validated_claims = result.unwrap();
2444 assert_eq!(validated_claims["iss"], "https://auth.example.com");
2445 assert_eq!(validated_claims["sub"], "test-user");
2446 }
2447
2448 #[test]
2449 fn test_validate_std_claims_success() {
2450 let provider = OidcProvider {
2451 issuer: "https://auth.example.com".to_string(),
2452 aud: Some("test-audience".to_string()),
2453 shared_secret: None,
2454 jwks_uri: "https://auth.example.com/jwks".to_string(),
2455 jwks: Arc::new(RwLock::new(None)),
2456 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2457 http: reqwest::Client::new(),
2458 rules: vec![],
2459 };
2460
2461 let claims = serde_json::json!({
2462 "iss": "https://auth.example.com",
2463 "aud": "test-audience",
2464 "sub": "test-user",
2465 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2466 });
2467
2468 let result = provider.validate_std_claims(&claims);
2469 assert!(result.is_ok());
2470 }
2471
2472 #[test]
2473 fn test_validate_std_claims_wrong_issuer() {
2474 let provider = OidcProvider {
2475 issuer: "https://auth.example.com".to_string(),
2476 aud: None,
2477 shared_secret: None,
2478 jwks_uri: "https://auth.example.com/jwks".to_string(),
2479 jwks: Arc::new(RwLock::new(None)),
2480 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2481 http: reqwest::Client::new(),
2482 rules: vec![],
2483 };
2484
2485 let claims = serde_json::json!({
2486 "iss": "https://wrong-issuer.com",
2487 "sub": "test-user",
2488 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2489 });
2490
2491 let result = provider.validate_std_claims(&claims);
2492 assert!(result.is_err());
2493
2494 if let Err(ProxyError::SecurityError(msg)) = result {
2495 assert!(msg.contains("Invalid issuer"));
2496 assert!(msg.contains("expected 'https://auth.example.com'"));
2497 assert!(msg.contains("got 'https://wrong-issuer.com'"));
2498 } else {
2499 panic!("Expected SecurityError");
2500 }
2501 }
2502
2503 #[test]
2504 fn test_validate_std_claims_missing_issuer() {
2505 let provider = OidcProvider {
2506 issuer: "https://auth.example.com".to_string(),
2507 aud: None,
2508 shared_secret: None,
2509 jwks_uri: "https://auth.example.com/jwks".to_string(),
2510 jwks: Arc::new(RwLock::new(None)),
2511 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2512 http: reqwest::Client::new(),
2513 rules: vec![],
2514 };
2515
2516 let claims = serde_json::json!({
2517 "sub": "test-user",
2518 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2519 });
2520
2521 let result = provider.validate_std_claims(&claims);
2522 assert!(result.is_err());
2523
2524 if let Err(ProxyError::SecurityError(msg)) = result {
2525 assert_eq!(msg, "Missing issuer claim");
2526 } else {
2527 panic!("Expected SecurityError");
2528 }
2529 }
2530
2531 #[test]
2532 fn test_validate_std_claims_audience_array_success() {
2533 let provider = OidcProvider {
2534 issuer: "https://auth.example.com".to_string(),
2535 aud: Some("test-audience".to_string()),
2536 shared_secret: None,
2537 jwks_uri: "https://auth.example.com/jwks".to_string(),
2538 jwks: Arc::new(RwLock::new(None)),
2539 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2540 http: reqwest::Client::new(),
2541 rules: vec![],
2542 };
2543
2544 let claims = serde_json::json!({
2545 "iss": "https://auth.example.com",
2546 "aud": ["other-audience", "test-audience", "another-audience"],
2547 "sub": "test-user",
2548 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2549 });
2550
2551 let result = provider.validate_std_claims(&claims);
2552 assert!(result.is_ok());
2553 }
2554
2555 #[test]
2556 fn test_validate_std_claims_audience_array_failure() {
2557 let provider = OidcProvider {
2558 issuer: "https://auth.example.com".to_string(),
2559 aud: Some("test-audience".to_string()),
2560 shared_secret: None,
2561 jwks_uri: "https://auth.example.com/jwks".to_string(),
2562 jwks: Arc::new(RwLock::new(None)),
2563 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2564 http: reqwest::Client::new(),
2565 rules: vec![],
2566 };
2567
2568 let claims = serde_json::json!({
2569 "iss": "https://auth.example.com",
2570 "aud": ["other-audience", "wrong-audience", "another-audience"],
2571 "sub": "test-user",
2572 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2573 });
2574
2575 let result = provider.validate_std_claims(&claims);
2576 assert!(result.is_err());
2577
2578 if let Err(ProxyError::SecurityError(msg)) = result {
2579 assert!(msg.contains("Invalid audience"));
2580 assert!(msg.contains("expected 'test-audience'"));
2581 } else {
2582 panic!("Expected SecurityError");
2583 }
2584 }
2585
2586 #[test]
2587 fn test_validate_std_claims_invalid_audience_type() {
2588 let provider = OidcProvider {
2589 issuer: "https://auth.example.com".to_string(),
2590 aud: Some("test-audience".to_string()),
2591 shared_secret: None,
2592 jwks_uri: "https://auth.example.com/jwks".to_string(),
2593 jwks: Arc::new(RwLock::new(None)),
2594 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2595 http: reqwest::Client::new(),
2596 rules: vec![],
2597 };
2598
2599 let claims = serde_json::json!({
2600 "iss": "https://auth.example.com",
2601 "aud": 12345, "sub": "test-user",
2603 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2604 });
2605
2606 let result = provider.validate_std_claims(&claims);
2607 assert!(result.is_err());
2608
2609 if let Err(ProxyError::SecurityError(msg)) = result {
2610 assert!(msg.contains("Invalid audience"));
2611 assert!(msg.contains("expected 'test-audience'"));
2612 } else {
2613 panic!("Expected SecurityError");
2614 }
2615 }
2616
2617 #[test]
2618 fn test_validate_std_claims_expired_token() {
2619 let provider = OidcProvider {
2620 issuer: "https://auth.example.com".to_string(),
2621 aud: None,
2622 shared_secret: None,
2623 jwks_uri: "https://auth.example.com/jwks".to_string(),
2624 jwks: Arc::new(RwLock::new(None)),
2625 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2626 http: reqwest::Client::new(),
2627 rules: vec![],
2628 };
2629
2630 let claims = serde_json::json!({
2631 "iss": "https://auth.example.com",
2632 "sub": "test-user",
2633 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - 3600) as i64 });
2635
2636 let result = provider.validate_std_claims(&claims);
2637 assert!(result.is_err());
2638
2639 if let Err(ProxyError::SecurityError(msg)) = result {
2640 assert!(msg.contains("Token expired"));
2641 } else {
2642 panic!("Expected SecurityError");
2643 }
2644 }
2645
2646 #[test]
2647 fn test_validate_std_claims_no_audience_configured() {
2648 let provider = OidcProvider {
2649 issuer: "https://auth.example.com".to_string(),
2650 aud: None, shared_secret: None,
2652 jwks_uri: "https://auth.example.com/jwks".to_string(),
2653 jwks: Arc::new(RwLock::new(None)),
2654 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2655 http: reqwest::Client::new(),
2656 rules: vec![],
2657 };
2658
2659 let claims = serde_json::json!({
2660 "iss": "https://auth.example.com",
2661 "aud": "any-audience", "sub": "test-user",
2663 "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2664 });
2665
2666 let result = provider.validate_std_claims(&claims);
2667 assert!(result.is_ok());
2668 }
2669
2670
2671
2672 #[tokio::test]
2673 async fn test_jwks_refresh_empty_keys() {
2674 let mock_server = MockServer::start().await;
2676
2677 Mock::given(method("GET"))
2679 .and(path("/"))
2680 .respond_with(ResponseTemplate::new(200)
2681 .set_body_json(serde_json::json!({
2682 "issuer": mock_server.uri(),
2683 "jwks_uri": format!("{}/jwks", mock_server.uri())
2684 })))
2685 .mount(&mock_server)
2686 .await;
2687
2688 Mock::given(method("GET"))
2690 .and(path("/jwks"))
2691 .respond_with(ResponseTemplate::new(200)
2692 .set_body_json(serde_json::json!({
2693 "keys": []
2694 })))
2695 .mount(&mock_server)
2696 .await;
2697
2698 let config = OidcConfig {
2699 issuer_uri: mock_server.uri(),
2700 aud: None,
2701 shared_secret: None,
2702 bypass: vec![],
2703 };
2704
2705 let provider = OidcProvider::discover(config).await.unwrap();
2706
2707 {
2709 let mut jwks_w = provider.jwks.write().await;
2710 *jwks_w = None;
2711 }
2712 {
2713 let mut refresh_w = provider.last_refresh.write().await;
2714 *refresh_w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH + std::time::Duration::from_secs(1))
2715 .unwrap_or_else(|| {
2716 tokio::time::Instant::now().checked_sub(std::time::Duration::from_millis(1))
2717 .unwrap_or_else(|| tokio::time::Instant::now())
2718 });
2719 }
2720
2721 let result = provider.refresh_jwks().await;
2722 assert!(result.is_ok());
2723
2724 let jwks = provider.jwks.read().await;
2726 assert!(jwks.is_some());
2727 let jwks = jwks.as_ref().unwrap();
2728 assert_eq!(jwks.keys.len(), 0);
2729 }
2730
2731 #[tokio::test]
2732 async fn test_oidc_provider_discover_glob_set_build_failure() {
2733 let mock_server = MockServer::start().await;
2739
2740 Mock::given(method("GET"))
2741 .and(path("/"))
2742 .respond_with(ResponseTemplate::new(200)
2743 .set_body_json(serde_json::json!({
2744 "issuer": mock_server.uri(),
2745 "jwks_uri": format!("{}/jwks", mock_server.uri())
2746 })))
2747 .mount(&mock_server)
2748 .await;
2749
2750 let config = OidcConfig {
2752 issuer_uri: mock_server.uri(),
2753 aud: None,
2754 shared_secret: None,
2755 bypass: vec![
2756 RouteRuleConfig {
2757 methods: vec!["GET".to_string()],
2758 path: "/api/*".to_string(), },
2760 ],
2761 };
2762
2763 let result = OidcProvider::discover(config).await;
2765 assert!(result.is_ok());
2766 }
2767
2768 #[tokio::test]
2769 async fn test_multiple_bypass_rules_overlapping() {
2770 let mock_server = MockServer::start().await;
2772
2773 Mock::given(method("GET"))
2774 .and(path("/"))
2775 .respond_with(ResponseTemplate::new(200)
2776 .set_body_json(serde_json::json!({
2777 "issuer": mock_server.uri(),
2778 "jwks_uri": format!("{}/jwks", mock_server.uri())
2779 })))
2780 .mount(&mock_server)
2781 .await;
2782
2783 let config = OidcConfig {
2784 issuer_uri: mock_server.uri(),
2785 aud: None,
2786 shared_secret: None,
2787 bypass: vec![
2788 RouteRuleConfig {
2789 methods: vec!["GET".to_string()],
2790 path: "/api/*".to_string(),
2791 },
2792 RouteRuleConfig {
2793 methods: vec!["*".to_string()],
2794 path: "/api/health".to_string(), },
2796 RouteRuleConfig {
2797 methods: vec!["POST".to_string(), "PUT".to_string()],
2798 path: "/api/users/*".to_string(),
2799 },
2800 ],
2801 };
2802
2803 let provider = OidcProvider::discover(config).await.unwrap();
2804 assert_eq!(provider.rules.len(), 3);
2805
2806 assert!(provider.is_bypassed("GET", "/api/health")); assert!(provider.is_bypassed("DELETE", "/api/health")); assert!(provider.is_bypassed("POST", "/api/users/123")); assert!(provider.is_bypassed("GET", "/api/users/123")); }
2812
2813 #[tokio::test]
2814 async fn test_authorization_header_with_extra_whitespace() {
2815 let provider = OidcProvider {
2816 issuer: "https://auth.example.com".to_string(),
2817 aud: None,
2818 shared_secret: Some("test-secret".to_string()),
2819 jwks_uri: "https://auth.example.com/jwks".to_string(),
2820 jwks: Arc::new(RwLock::new(None)),
2821 last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2822 http: reqwest::Client::new(),
2823 rules: vec![],
2824 };
2825
2826 let mut headers = HeaderMap::new();
2829 headers.insert("authorization", " Bearer token123 ".parse().unwrap());
2830
2831 let request = ProxyRequest {
2832 method: HttpMethod::Get,
2833 path: "/api/users".to_string(),
2834 query: None,
2835 headers,
2836 body: reqwest::Body::from(""),
2837 context: Arc::new(RwLock::new(RequestContext::default())),
2838 custom_target: None,
2839 };
2840
2841 let result = provider.pre(request).await;
2842 assert!(result.is_err());
2843
2844 if let Err(ProxyError::SecurityError(msg)) = result {
2846 assert!(msg.contains("Invalid authorization scheme"));
2847 } else {
2848 panic!("Expected SecurityError");
2849 }
2850 }
2851
2852 #[test]
2853 fn test_route_rule_matches_edge_cases() {
2854 let mut builder = GlobSetBuilder::new();
2855 builder.add(Glob::new("/**").unwrap()); let paths = builder.build().unwrap();
2857
2858 let rule = RouteRule {
2859 methods: vec!["GET".to_string(), "POST".to_string()],
2860 paths,
2861 };
2862
2863 assert!(rule.matches("GET", "/"));
2865 assert!(rule.matches("POST", "/api"));
2866 assert!(rule.matches("GET", "/api/v1/users/123"));
2867 assert!(rule.matches("POST", "/very/deep/nested/path/structure"));
2868 assert!(!rule.matches("DELETE", "/api")); assert!(!rule.matches("PUT", "/")); }
2871
2872 #[test]
2873 fn test_route_rule_matches_empty_methods() {
2874 let mut builder = GlobSetBuilder::new();
2875 builder.add(Glob::new("/health").unwrap());
2876 let paths = builder.build().unwrap();
2877
2878 let rule = RouteRule {
2879 methods: vec![], paths,
2881 };
2882
2883 assert!(!rule.matches("GET", "/health"));
2885 assert!(!rule.matches("POST", "/health"));
2886 assert!(!rule.matches("*", "/health"));
2887 }
2888}
2889
2890#[async_trait]
2891impl SecurityProvider for OidcProvider {
2892 fn name(&self) -> &str { "OidcProvider" }
2893
2894 fn stage(&self) -> SecurityStage { SecurityStage::Pre }
2895
2896 async fn pre(&self, req: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
2897 if self.is_bypassed(&req.method.to_string(), &req.path) {
2899 debug_fmt!("OidcProvider", "OIDC bypass for {} {}", req.method, req.path);
2900 return Ok(req);
2901 }
2902
2903 debug_fmt!("OidcProvider", "OIDC validating request: {} {}", req.method, req.path);
2904
2905 let auth_header = match req.headers.get("authorization") {
2907 Some(h) => match h.to_str() {
2908 Ok(s) => s.to_lowercase(),
2909 Err(e) => {
2910 let err = ProxyError::SecurityError(
2911 format!("Invalid authorization header: {e}")
2912 );
2913 warn_fmt!("OidcProvider", "{}", err);
2914 return Err(err);
2915 }
2916 },
2917 None => {
2918 let err = ProxyError::SecurityError("Missing authorization header".to_string());
2919 warn_fmt!("OidcProvider", "{}", err);
2920 return Err(err);
2921 }
2922 };
2923
2924 if !auth_header.starts_with(BEARER) {
2925 let err = ProxyError::SecurityError(
2926 format!("Invalid authorization scheme: expected 'Bearer', got '{}'",
2927 auth_header.split_whitespace().next().unwrap_or(""))
2928 );
2929 warn_fmt!("OidcProvider", "{}", err);
2930 return Err(err);
2931 }
2932
2933 let token = &auth_header[BEARER.len()..];
2934 if token.is_empty() {
2935 let err = ProxyError::SecurityError("Empty bearer token".to_string());
2936 warn_fmt!("OidcProvider", "{}", err);
2937 return Err(err);
2938 }
2939
2940 trace_fmt!("OidcProvider", "Validating token: {}", token);
2942 let claims = match self.validate_token(token).await {
2943 Ok(claims) => claims,
2944 Err(e) => {
2945 warn_fmt!("OidcProvider", "Token validation failed: {}", e);
2946 return Err(e);
2947 }
2948 };
2949
2950 {
2952 let mut ctx = req.context.write().await;
2953 ctx.attributes.insert(CLAIMS_ATTRIBUTE.to_string(), claims);
2954 }
2955
2956 debug_fmt!("OidcProvider", "OIDC validation successful for {} {}", req.method, req.path);
2957 Ok(req)
2958 }
2959}