1use std::{collections::HashMap, sync::Arc};
6
7use chrono::{DateTime, Duration, Utc};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct TokenResponse {
13 pub access_token: String,
15 pub refresh_token: Option<String>,
17 pub token_type: String,
19 pub expires_in: u64,
21 pub id_token: Option<String>,
23 pub scope: Option<String>,
25}
26
27impl TokenResponse {
28 pub fn new(access_token: String, token_type: String, expires_in: u64) -> Self {
30 Self {
31 access_token,
32 refresh_token: None,
33 token_type,
34 expires_in,
35 id_token: None,
36 scope: None,
37 }
38 }
39
40 pub fn expiry_time(&self) -> DateTime<Utc> {
42 Utc::now() + Duration::seconds(self.expires_in as i64)
43 }
44
45 pub fn is_expired(&self) -> bool {
47 self.expiry_time() <= Utc::now()
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct IdTokenClaims {
54 pub iss: String,
56 pub sub: String,
58 pub aud: String,
60 pub exp: i64,
62 pub iat: i64,
64 pub auth_time: Option<i64>,
66 pub nonce: Option<String>,
68 pub email: Option<String>,
70 pub email_verified: Option<bool>,
72 pub name: Option<String>,
74 pub picture: Option<String>,
76 pub locale: Option<String>,
78}
79
80impl IdTokenClaims {
81 pub fn new(iss: String, sub: String, aud: String, exp: i64, iat: i64) -> Self {
83 Self {
84 iss,
85 sub,
86 aud,
87 exp,
88 iat,
89 auth_time: None,
90 nonce: None,
91 email: None,
92 email_verified: None,
93 name: None,
94 picture: None,
95 locale: None,
96 }
97 }
98
99 pub fn is_expired(&self) -> bool {
101 self.exp <= Utc::now().timestamp()
102 }
103
104 pub fn is_expiring_soon(&self, grace_seconds: i64) -> bool {
106 self.exp <= (Utc::now().timestamp() + grace_seconds)
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct UserInfo {
113 pub sub: String,
115 pub email: Option<String>,
117 pub email_verified: Option<bool>,
119 pub name: Option<String>,
121 pub picture: Option<String>,
123 pub locale: Option<String>,
125}
126
127impl UserInfo {
128 pub fn new(sub: String) -> Self {
130 Self {
131 sub,
132 email: None,
133 email_verified: None,
134 name: None,
135 picture: None,
136 locale: None,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
143pub struct OIDCProviderConfig {
144 pub issuer: String,
146 pub authorization_endpoint: String,
148 pub token_endpoint: String,
150 pub userinfo_endpoint: Option<String>,
152 pub jwks_uri: String,
154 pub scopes_supported: Vec<String>,
156 pub response_types_supported: Vec<String>,
158}
159
160impl OIDCProviderConfig {
161 pub fn new(
163 issuer: String,
164 authorization_endpoint: String,
165 token_endpoint: String,
166 jwks_uri: String,
167 ) -> Self {
168 Self {
169 issuer,
170 authorization_endpoint,
171 token_endpoint,
172 userinfo_endpoint: None,
173 jwks_uri,
174 scopes_supported: vec![
175 "openid".to_string(),
176 "profile".to_string(),
177 "email".to_string(),
178 ],
179 response_types_supported: vec!["code".to_string()],
180 }
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct OAuth2Client {
187 pub client_id: String,
189 #[allow(dead_code)]
191 client_secret: String,
192 pub authorization_endpoint: String,
194 #[allow(dead_code)]
196 token_endpoint: String,
197 pub scopes: Vec<String>,
199 pub use_pkce: bool,
201}
202
203impl OAuth2Client {
204 pub fn new(
206 client_id: impl Into<String>,
207 client_secret: impl Into<String>,
208 authorization_endpoint: impl Into<String>,
209 token_endpoint: impl Into<String>,
210 ) -> Self {
211 Self {
212 client_id: client_id.into(),
213 client_secret: client_secret.into(),
214 authorization_endpoint: authorization_endpoint.into(),
215 token_endpoint: token_endpoint.into(),
216 scopes: vec![
217 "openid".to_string(),
218 "profile".to_string(),
219 "email".to_string(),
220 ],
221 use_pkce: false,
222 }
223 }
224
225 pub fn with_scopes(mut self, scopes: Vec<String>) -> Self {
227 self.scopes = scopes;
228 self
229 }
230
231 pub fn with_pkce(mut self, enabled: bool) -> Self {
233 self.use_pkce = enabled;
234 self
235 }
236
237 pub fn authorization_url(&self, redirect_uri: &str) -> Result<String, String> {
239 let state = uuid::Uuid::new_v4().to_string();
240 let scope = self.scopes.join(" ");
241
242 let url = format!(
243 "{}?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}",
244 self.authorization_endpoint,
245 urlencoding::encode(&self.client_id),
246 urlencoding::encode(redirect_uri),
247 urlencoding::encode(&scope),
248 urlencoding::encode(&state),
249 );
250
251 Ok(url)
252 }
253
254 pub async fn exchange_code(
256 &self,
257 _code: &str,
258 _redirect_uri: &str,
259 ) -> Result<TokenResponse, String> {
260 Ok(TokenResponse {
262 access_token: format!("access_token_{}", uuid::Uuid::new_v4()),
263 refresh_token: Some(format!("refresh_token_{}", uuid::Uuid::new_v4())),
264 token_type: "Bearer".to_string(),
265 expires_in: 3600,
266 id_token: Some("mock_id_token".to_string()),
267 scope: Some(self.scopes.join(" ")),
268 })
269 }
270
271 pub async fn refresh_token(&self, refresh_token: &str) -> Result<TokenResponse, String> {
273 Ok(TokenResponse {
275 access_token: format!("access_token_{}", uuid::Uuid::new_v4()),
276 refresh_token: Some(refresh_token.to_string()),
277 token_type: "Bearer".to_string(),
278 expires_in: 3600,
279 id_token: None,
280 scope: Some(self.scopes.join(" ")),
281 })
282 }
283}
284
285#[derive(Debug, Clone)]
287pub struct OIDCClient {
288 pub config: OIDCProviderConfig,
290 pub client_id: String,
292 #[allow(dead_code)]
294 client_secret: String,
295}
296
297impl OIDCClient {
298 pub fn new(
300 config: OIDCProviderConfig,
301 client_id: impl Into<String>,
302 client_secret: impl Into<String>,
303 ) -> Self {
304 Self {
305 config,
306 client_id: client_id.into(),
307 client_secret: client_secret.into(),
308 }
309 }
310
311 pub fn verify_id_token(
313 &self,
314 _id_token: &str,
315 expected_nonce: Option<&str>,
316 ) -> Result<IdTokenClaims, String> {
317 let claims = IdTokenClaims {
319 iss: self.config.issuer.clone(),
320 sub: "user_123".to_string(),
321 aud: self.client_id.clone(),
322 exp: (Utc::now() + Duration::hours(1)).timestamp(),
323 iat: Utc::now().timestamp(),
324 auth_time: Some(Utc::now().timestamp()),
325 nonce: expected_nonce.map(|s| s.to_string()),
326 email: Some("user@example.com".to_string()),
327 email_verified: Some(true),
328 name: Some("Test User".to_string()),
329 picture: None,
330 locale: Some("en-US".to_string()),
331 };
332
333 if let Some(expected) = expected_nonce {
335 if claims.nonce.as_deref() != Some(expected) {
336 return Err("Nonce mismatch".to_string());
337 }
338 }
339
340 Ok(claims)
341 }
342
343 pub async fn get_userinfo(&self, _access_token: &str) -> Result<UserInfo, String> {
345 Ok(UserInfo {
347 sub: "user_123".to_string(),
348 email: Some("user@example.com".to_string()),
349 email_verified: Some(true),
350 name: Some("Test User".to_string()),
351 picture: None,
352 locale: Some("en-US".to_string()),
353 })
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
359pub enum ProviderType {
360 OAuth2,
362 OIDC,
364}
365
366impl std::fmt::Display for ProviderType {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 match self {
369 Self::OAuth2 => write!(f, "oauth2"),
370 Self::OIDC => write!(f, "oidc"),
371 }
372 }
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct OAuthSession {
378 pub id: String,
380 pub user_id: String,
382 pub provider_type: ProviderType,
384 pub provider_name: String,
386 pub provider_user_id: String,
388 pub access_token: String,
390 pub refresh_token: Option<String>,
392 pub token_expiry: DateTime<Utc>,
394 pub created_at: DateTime<Utc>,
396 pub last_refreshed: Option<DateTime<Utc>>,
398}
399
400impl OAuthSession {
401 pub fn new(
403 user_id: String,
404 provider_type: ProviderType,
405 provider_name: String,
406 provider_user_id: String,
407 access_token: String,
408 token_expiry: DateTime<Utc>,
409 ) -> Self {
410 Self {
411 id: uuid::Uuid::new_v4().to_string(),
412 user_id,
413 provider_type,
414 provider_name,
415 provider_user_id,
416 access_token,
417 refresh_token: None,
418 token_expiry,
419 created_at: Utc::now(),
420 last_refreshed: None,
421 }
422 }
423
424 pub fn is_expired(&self) -> bool {
426 self.token_expiry <= Utc::now()
427 }
428
429 pub fn is_expiring_soon(&self, grace_seconds: i64) -> bool {
431 self.token_expiry <= (Utc::now() + Duration::seconds(grace_seconds))
432 }
433
434 pub fn refresh_tokens(&mut self, access_token: String, token_expiry: DateTime<Utc>) {
436 self.access_token = access_token;
437 self.token_expiry = token_expiry;
438 self.last_refreshed = Some(Utc::now());
439 }
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
444pub struct ExternalAuthProvider {
445 pub id: String,
447 pub provider_type: ProviderType,
449 pub provider_name: String,
451 pub client_id: String,
453 pub client_secret_vault_path: String,
455 pub oidc_config: Option<OIDCProviderConfig>,
457 pub oauth2_config: Option<OAuth2ClientConfig>,
459 pub enabled: bool,
461 pub scopes: Vec<String>,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
467pub struct OAuth2ClientConfig {
468 pub authorization_endpoint: String,
470 pub token_endpoint: String,
472 pub use_pkce: bool,
474}
475
476impl ExternalAuthProvider {
477 pub fn new(
479 provider_type: ProviderType,
480 provider_name: impl Into<String>,
481 client_id: impl Into<String>,
482 client_secret_vault_path: impl Into<String>,
483 ) -> Self {
484 Self {
485 id: uuid::Uuid::new_v4().to_string(),
486 provider_type,
487 provider_name: provider_name.into(),
488 client_id: client_id.into(),
489 client_secret_vault_path: client_secret_vault_path.into(),
490 oidc_config: None,
491 oauth2_config: None,
492 enabled: true,
493 scopes: vec![
494 "openid".to_string(),
495 "profile".to_string(),
496 "email".to_string(),
497 ],
498 }
499 }
500
501 pub fn set_enabled(&mut self, enabled: bool) {
503 self.enabled = enabled;
504 }
505
506 pub fn set_scopes(&mut self, scopes: Vec<String>) {
508 self.scopes = scopes;
509 }
510}
511
512#[derive(Debug, Clone)]
514pub struct ProviderRegistry {
515 providers: Arc<std::sync::Mutex<HashMap<String, ExternalAuthProvider>>>,
517}
518
519impl ProviderRegistry {
520 pub fn new() -> Self {
522 Self {
523 providers: Arc::new(std::sync::Mutex::new(HashMap::new())),
524 }
525 }
526
527 pub fn register(&self, provider: ExternalAuthProvider) -> Result<(), String> {
529 let mut providers = self.providers.lock().map_err(|_| "Lock failed".to_string())?;
530 providers.insert(provider.provider_name.clone(), provider);
531 Ok(())
532 }
533
534 pub fn get(&self, name: &str) -> Result<Option<ExternalAuthProvider>, String> {
536 let providers = self.providers.lock().map_err(|_| "Lock failed".to_string())?;
537 Ok(providers.get(name).cloned())
538 }
539
540 pub fn list_enabled(&self) -> Result<Vec<ExternalAuthProvider>, String> {
542 let providers = self.providers.lock().map_err(|_| "Lock failed".to_string())?;
543 Ok(providers.values().filter(|p| p.enabled).cloned().collect())
544 }
545
546 pub fn disable(&self, name: &str) -> Result<bool, String> {
548 let mut providers = self.providers.lock().map_err(|_| "Lock failed".to_string())?;
549 if let Some(provider) = providers.get_mut(name) {
550 provider.set_enabled(false);
551 Ok(true)
552 } else {
553 Ok(false)
554 }
555 }
556
557 pub fn enable(&self, name: &str) -> Result<bool, String> {
559 let mut providers = self.providers.lock().map_err(|_| "Lock failed".to_string())?;
560 if let Some(provider) = providers.get_mut(name) {
561 provider.set_enabled(true);
562 Ok(true)
563 } else {
564 Ok(false)
565 }
566 }
567}
568
569impl Default for ProviderRegistry {
570 fn default() -> Self {
571 Self::new()
572 }
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct PKCEChallenge {
578 pub code_verifier: String,
580 pub code_challenge: String,
582 pub code_challenge_method: String,
584}
585
586impl PKCEChallenge {
587 pub fn new() -> Self {
589 use sha2::{Digest, Sha256};
590
591 let verifier = format!("{}", uuid::Uuid::new_v4());
593
594 let mut hasher = Sha256::new();
596 hasher.update(verifier.as_bytes());
597 let digest = hasher.finalize();
598 let challenge = urlencoding::encode_binary(&digest).to_string();
599
600 Self {
601 code_verifier: verifier,
602 code_challenge: challenge,
603 code_challenge_method: "S256".to_string(),
604 }
605 }
606
607 pub fn verify(&self, verifier: &str) -> bool {
609 use sha2::{Digest, Sha256};
610
611 let mut hasher = Sha256::new();
612 hasher.update(verifier.as_bytes());
613 let digest = hasher.finalize();
614 let computed_challenge = urlencoding::encode_binary(&digest).to_string();
615
616 computed_challenge == self.code_challenge
617 }
618}
619
620impl Default for PKCEChallenge {
621 fn default() -> Self {
622 Self::new()
623 }
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
628pub struct StateParameter {
629 pub state: String,
631 pub expires_at: DateTime<Utc>,
633}
634
635impl StateParameter {
636 pub fn new() -> Self {
638 Self {
639 state: uuid::Uuid::new_v4().to_string(),
640 expires_at: Utc::now() + Duration::minutes(10),
641 }
642 }
643
644 pub fn is_expired(&self) -> bool {
646 self.expires_at <= Utc::now()
647 }
648
649 pub fn verify(&self, provided_state: &str) -> bool {
651 self.state == provided_state && !self.is_expired()
652 }
653}
654
655impl Default for StateParameter {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize)]
663pub struct NonceParameter {
664 pub nonce: String,
666 pub expires_at: DateTime<Utc>,
668}
669
670impl NonceParameter {
671 pub fn new() -> Self {
673 Self {
674 nonce: uuid::Uuid::new_v4().to_string(),
675 expires_at: Utc::now() + Duration::minutes(10),
676 }
677 }
678
679 pub fn is_expired(&self) -> bool {
681 self.expires_at <= Utc::now()
682 }
683
684 pub fn verify(&self, provided_nonce: &str) -> bool {
686 self.nonce == provided_nonce && !self.is_expired()
687 }
688}
689
690impl Default for NonceParameter {
691 fn default() -> Self {
692 Self::new()
693 }
694}
695
696#[derive(Debug, Clone)]
698pub struct TokenRefreshScheduler {
699 refresh_queue: Arc<std::sync::Mutex<Vec<(String, DateTime<Utc>)>>>,
701}
702
703impl TokenRefreshScheduler {
704 pub fn new() -> Self {
706 Self {
707 refresh_queue: Arc::new(std::sync::Mutex::new(Vec::new())),
708 }
709 }
710
711 pub fn schedule_refresh(
713 &self,
714 session_id: String,
715 refresh_time: DateTime<Utc>,
716 ) -> Result<(), String> {
717 let mut queue = self.refresh_queue.lock().map_err(|_| "Lock failed".to_string())?;
718 queue.push((session_id, refresh_time));
719 queue.sort_by_key(|(_, time)| *time);
720 Ok(())
721 }
722
723 pub fn get_next_refresh(&self) -> Result<Option<String>, String> {
725 let mut queue = self.refresh_queue.lock().map_err(|_| "Lock failed".to_string())?;
726 if let Some((_, refresh_time)) = queue.first() {
727 if *refresh_time <= Utc::now() {
728 let (id, _) = queue.remove(0);
729 return Ok(Some(id));
730 }
731 }
732 Ok(None)
733 }
734
735 pub fn cancel_refresh(&self, session_id: &str) -> Result<bool, String> {
737 let mut queue = self.refresh_queue.lock().map_err(|_| "Lock failed".to_string())?;
738 let len_before = queue.len();
739 queue.retain(|(id, _)| id != session_id);
740 Ok(queue.len() < len_before)
741 }
742}
743
744impl Default for TokenRefreshScheduler {
745 fn default() -> Self {
746 Self::new()
747 }
748}
749
750#[derive(Debug, Clone)]
752pub struct ProviderFailoverManager {
753 primary_provider: String,
755 fallback_providers: Vec<String>,
757 unavailable: Arc<std::sync::Mutex<Vec<(String, DateTime<Utc>)>>>,
759}
760
761impl ProviderFailoverManager {
762 pub fn new(primary: String, fallbacks: Vec<String>) -> Self {
764 Self {
765 primary_provider: primary,
766 fallback_providers: fallbacks,
767 unavailable: Arc::new(std::sync::Mutex::new(Vec::new())),
768 }
769 }
770
771 pub fn get_available_provider(&self) -> Result<String, String> {
773 let unavailable = self.unavailable.lock().map_err(|_| "Lock failed".to_string())?;
774 let now = Utc::now();
775
776 if !unavailable
778 .iter()
779 .any(|(name, exp)| name == &self.primary_provider && *exp > now)
780 {
781 return Ok(self.primary_provider.clone());
782 }
783
784 for fallback in &self.fallback_providers {
786 if !unavailable.iter().any(|(name, exp)| name == fallback && *exp > now) {
787 return Ok(fallback.clone());
788 }
789 }
790
791 Err("No providers available".to_string())
792 }
793
794 pub fn mark_unavailable(&self, provider: String, duration_seconds: u64) -> Result<(), String> {
796 let mut unavailable = self.unavailable.lock().map_err(|_| "Lock failed".to_string())?;
797 unavailable.push((provider, Utc::now() + Duration::seconds(duration_seconds as i64)));
798 Ok(())
799 }
800
801 pub fn mark_available(&self, provider: &str) -> Result<(), String> {
803 let mut unavailable = self.unavailable.lock().map_err(|_| "Lock failed".to_string())?;
804 unavailable.retain(|(name, _)| name != provider);
805 Ok(())
806 }
807}
808
809#[derive(Debug, Clone, Serialize, Deserialize)]
811pub struct OAuthAuditEvent {
812 pub event_type: String,
814 pub provider: String,
816 pub user_id: Option<String>,
818 pub status: String,
820 pub error: Option<String>,
822 pub timestamp: DateTime<Utc>,
824 pub metadata: HashMap<String, String>,
826}
827
828impl OAuthAuditEvent {
829 pub fn new(
831 event_type: impl Into<String>,
832 provider: impl Into<String>,
833 status: impl Into<String>,
834 ) -> Self {
835 Self {
836 event_type: event_type.into(),
837 provider: provider.into(),
838 user_id: None,
839 status: status.into(),
840 error: None,
841 timestamp: Utc::now(),
842 metadata: HashMap::new(),
843 }
844 }
845
846 pub fn with_user_id(mut self, user_id: String) -> Self {
848 self.user_id = Some(user_id);
849 self
850 }
851
852 pub fn with_error(mut self, error: String) -> Self {
854 self.error = Some(error);
855 self
856 }
857
858 pub fn with_metadata(mut self, key: String, value: String) -> Self {
860 self.metadata.insert(key, value);
861 self
862 }
863}
864
865#[cfg(test)]
866mod tests {
867 use super::*;
868
869 #[test]
870 fn test_token_response_creation() {
871 let token = TokenResponse::new("token123".to_string(), "Bearer".to_string(), 3600);
872 assert_eq!(token.access_token, "token123");
873 assert_eq!(token.token_type, "Bearer");
874 assert_eq!(token.expires_in, 3600);
875 }
876
877 #[test]
878 fn test_token_response_expiry_calculation() {
879 let token = TokenResponse::new("token123".to_string(), "Bearer".to_string(), 3600);
880 assert!(!token.is_expired());
881 }
882
883 #[test]
884 fn test_id_token_claims_creation() {
885 let exp = (Utc::now() + Duration::hours(1)).timestamp();
886 let claims = IdTokenClaims::new(
887 "https://provider.com".to_string(),
888 "user123".to_string(),
889 "client_id".to_string(),
890 exp,
891 Utc::now().timestamp(),
892 );
893 assert_eq!(claims.sub, "user123");
894 assert!(!claims.is_expired());
895 }
896
897 #[test]
898 fn test_id_token_claims_expiry() {
899 let exp = (Utc::now() - Duration::hours(1)).timestamp();
900 let claims = IdTokenClaims::new(
901 "https://provider.com".to_string(),
902 "user123".to_string(),
903 "client_id".to_string(),
904 exp,
905 (Utc::now() - Duration::hours(2)).timestamp(),
906 );
907 assert!(claims.is_expired());
908 }
909
910 #[test]
911 fn test_userinfo_creation() {
912 let userinfo = UserInfo::new("user123".to_string());
913 assert_eq!(userinfo.sub, "user123");
914 assert!(userinfo.email.is_none());
915 }
916
917 #[test]
918 fn test_oauth2_client_creation() {
919 let client = OAuth2Client::new(
920 "client_id",
921 "client_secret",
922 "https://provider.com/authorize",
923 "https://provider.com/token",
924 );
925 assert_eq!(client.client_id, "client_id");
926 }
927
928 #[test]
929 fn test_oauth2_client_with_scopes() {
930 let scopes = vec!["openid".to_string(), "profile".to_string()];
931 let client = OAuth2Client::new(
932 "client_id",
933 "client_secret",
934 "https://provider.com/authorize",
935 "https://provider.com/token",
936 )
937 .with_scopes(scopes.clone());
938 assert_eq!(client.scopes, scopes);
939 }
940
941 #[test]
942 fn test_oidc_provider_config_creation() {
943 let config = OIDCProviderConfig::new(
944 "https://provider.com".to_string(),
945 "https://provider.com/authorize".to_string(),
946 "https://provider.com/token".to_string(),
947 "https://provider.com/jwks".to_string(),
948 );
949 assert_eq!(config.issuer, "https://provider.com");
950 }
951
952 #[test]
953 fn test_oauth_session_creation() {
954 let session = OAuthSession::new(
955 "user_123".to_string(),
956 ProviderType::OIDC,
957 "auth0".to_string(),
958 "auth0|user_id".to_string(),
959 "access_token".to_string(),
960 Utc::now() + Duration::hours(1),
961 );
962 assert_eq!(session.user_id, "user_123");
963 assert!(!session.is_expired());
964 }
965
966 #[test]
967 fn test_oauth_session_token_refresh() {
968 let mut session = OAuthSession::new(
969 "user_123".to_string(),
970 ProviderType::OIDC,
971 "auth0".to_string(),
972 "auth0|user_id".to_string(),
973 "old_token".to_string(),
974 Utc::now() + Duration::hours(1),
975 );
976 let new_expiry = Utc::now() + Duration::hours(2);
977 session.refresh_tokens("new_token".to_string(), new_expiry);
978 assert_eq!(session.access_token, "new_token");
979 assert!(session.last_refreshed.is_some());
980 }
981
982 #[test]
983 fn test_external_auth_provider_creation() {
984 let provider = ExternalAuthProvider::new(
985 ProviderType::OIDC,
986 "auth0",
987 "client_id",
988 "vault/path/to/secret",
989 );
990 assert_eq!(provider.provider_name, "auth0");
991 assert!(provider.enabled);
992 }
993
994 #[test]
995 fn test_provider_registry_register_and_get() {
996 let registry = ProviderRegistry::new();
997 let provider =
998 ExternalAuthProvider::new(ProviderType::OIDC, "auth0", "client_id", "vault/path");
999 registry.register(provider.clone()).unwrap();
1000 let retrieved = registry.get("auth0").unwrap();
1001 assert_eq!(retrieved, Some(provider));
1002 }
1003
1004 #[test]
1005 fn test_provider_registry_list_enabled() {
1006 let registry = ProviderRegistry::new();
1007 let provider1 = ExternalAuthProvider::new(ProviderType::OIDC, "auth0", "id1", "path1");
1008 let provider2 = ExternalAuthProvider::new(ProviderType::OAuth2, "google", "id2", "path2");
1009 registry.register(provider1).unwrap();
1010 registry.register(provider2).unwrap();
1011 let enabled = registry.list_enabled().unwrap();
1012 assert_eq!(enabled.len(), 2);
1013 }
1014
1015 #[test]
1016 fn test_provider_registry_disable_enable() {
1017 let registry = ProviderRegistry::new();
1018 let provider = ExternalAuthProvider::new(ProviderType::OIDC, "auth0", "id", "path");
1019 registry.register(provider).unwrap();
1020
1021 registry.disable("auth0").unwrap();
1022 let retrieved = registry.get("auth0").unwrap();
1023 assert!(!retrieved.unwrap().enabled);
1024
1025 registry.enable("auth0").unwrap();
1026 let retrieved = registry.get("auth0").unwrap();
1027 assert!(retrieved.unwrap().enabled);
1028 }
1029
1030 #[test]
1031 fn test_pkce_challenge_generation() {
1032 let challenge = PKCEChallenge::new();
1033 assert!(!challenge.code_verifier.is_empty());
1034 assert!(!challenge.code_challenge.is_empty());
1035 assert_eq!(challenge.code_challenge_method, "S256");
1036 }
1037
1038 #[test]
1039 fn test_pkce_verification() {
1040 let challenge = PKCEChallenge::new();
1041 let verifier = challenge.code_verifier.clone();
1042 assert!(challenge.verify(&verifier));
1043 }
1044
1045 #[test]
1046 fn test_pkce_verification_fails_with_wrong_verifier() {
1047 let challenge = PKCEChallenge::new();
1048 assert!(!challenge.verify("wrong_verifier"));
1049 }
1050
1051 #[test]
1052 fn test_state_parameter_generation() {
1053 let state = StateParameter::new();
1054 assert!(!state.state.is_empty());
1055 assert!(!state.is_expired());
1056 }
1057
1058 #[test]
1059 fn test_state_parameter_verification() {
1060 let state = StateParameter::new();
1061 assert!(state.verify(&state.state));
1062 }
1063
1064 #[test]
1065 fn test_state_parameter_verification_fails_with_wrong_state() {
1066 let state = StateParameter::new();
1067 assert!(!state.verify("wrong_state"));
1068 }
1069
1070 #[test]
1071 fn test_nonce_parameter_generation() {
1072 let nonce = NonceParameter::new();
1073 assert!(!nonce.nonce.is_empty());
1074 assert!(!nonce.is_expired());
1075 }
1076
1077 #[test]
1078 fn test_nonce_parameter_verification() {
1079 let nonce = NonceParameter::new();
1080 assert!(nonce.verify(&nonce.nonce));
1081 }
1082
1083 #[test]
1084 fn test_token_refresh_scheduler_schedule_and_retrieve() {
1085 let scheduler = TokenRefreshScheduler::new();
1086 let refresh_time = Utc::now() - Duration::seconds(10);
1087 scheduler.schedule_refresh("session_1".to_string(), refresh_time).unwrap();
1088
1089 let next = scheduler.get_next_refresh().unwrap();
1090 assert_eq!(next, Some("session_1".to_string()));
1091 }
1092
1093 #[test]
1094 fn test_token_refresh_scheduler_cancel() {
1095 let scheduler = TokenRefreshScheduler::new();
1096 let refresh_time = Utc::now() + Duration::hours(1);
1097 scheduler.schedule_refresh("session_1".to_string(), refresh_time).unwrap();
1098
1099 let cancelled = scheduler.cancel_refresh("session_1").unwrap();
1100 assert!(cancelled);
1101 }
1102
1103 #[test]
1104 fn test_failover_manager_primary_available() {
1105 let manager = ProviderFailoverManager::new("auth0".to_string(), vec!["google".to_string()]);
1106 let available = manager.get_available_provider().unwrap();
1107 assert_eq!(available, "auth0");
1108 }
1109
1110 #[test]
1111 fn test_failover_manager_fallback() {
1112 let manager = ProviderFailoverManager::new("auth0".to_string(), vec!["google".to_string()]);
1113 manager.mark_unavailable("auth0".to_string(), 300).unwrap();
1114 let available = manager.get_available_provider().unwrap();
1115 assert_eq!(available, "google");
1116 }
1117
1118 #[test]
1119 fn test_failover_manager_mark_available() {
1120 let manager = ProviderFailoverManager::new("auth0".to_string(), vec!["google".to_string()]);
1121 manager.mark_unavailable("auth0".to_string(), 300).unwrap();
1122 manager.mark_available("auth0").unwrap();
1123 let available = manager.get_available_provider().unwrap();
1124 assert_eq!(available, "auth0");
1125 }
1126
1127 #[test]
1128 fn test_oauth_audit_event_creation() {
1129 let event = OAuthAuditEvent::new("authorization", "auth0", "success");
1130 assert_eq!(event.event_type, "authorization");
1131 assert_eq!(event.provider, "auth0");
1132 assert_eq!(event.status, "success");
1133 }
1134
1135 #[test]
1136 fn test_oauth_audit_event_with_user_id() {
1137 let event = OAuthAuditEvent::new("token_exchange", "auth0", "success")
1138 .with_user_id("user_123".to_string());
1139 assert_eq!(event.user_id, Some("user_123".to_string()));
1140 }
1141
1142 #[test]
1143 fn test_oauth_audit_event_with_error() {
1144 let event = OAuthAuditEvent::new("token_exchange", "auth0", "failed")
1145 .with_error("Provider unavailable".to_string());
1146 assert_eq!(event.error, Some("Provider unavailable".to_string()));
1147 }
1148
1149 #[test]
1150 fn test_oauth_audit_event_with_metadata() {
1151 let event = OAuthAuditEvent::new("authorization", "auth0", "success")
1152 .with_metadata("ip_address".to_string(), "192.168.1.1".to_string());
1153 assert_eq!(event.metadata.get("ip_address"), Some(&"192.168.1.1".to_string()));
1154 }
1155}