1use crate::errors::{AuthError, Result};
49use serde::{Deserialize, Serialize};
50use std::collections::HashMap;
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54#[serde(rename_all = "snake_case")]
55pub enum OidcErrorCode {
56 InvalidRequest,
58 InvalidClient,
59 InvalidGrant,
60 UnauthorizedClient,
61 UnsupportedGrantType,
62 InvalidScope,
63
64 InteractionRequired,
66 LoginRequired,
67 AccountSelectionRequired,
68 ConsentRequired,
69 InvalidRequestUri,
70 InvalidRequestObject,
71 RequestNotSupported,
72 RequestUriNotSupported,
73 RegistrationNotSupported,
74
75 UnmetAuthenticationRequirements,
78 UnmetAuthenticationContextRequirements,
80 SessionSelectionRequired,
82 AuthenticationMethodRequired,
84 InsufficientIdentityAssurance,
86 TemporarilyUnavailable,
88 RegistrationRequired,
90 UnsupportedPromptValue,
92 UserSelectionRequired,
94}
95
96impl std::fmt::Display for OidcErrorCode {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 let s = match self {
99 Self::InvalidRequest => "invalid_request",
100 Self::InvalidClient => "invalid_client",
101 Self::InvalidGrant => "invalid_grant",
102 Self::UnauthorizedClient => "unauthorized_client",
103 Self::UnsupportedGrantType => "unsupported_grant_type",
104 Self::InvalidScope => "invalid_scope",
105 Self::InteractionRequired => "interaction_required",
106 Self::LoginRequired => "login_required",
107 Self::AccountSelectionRequired => "account_selection_required",
108 Self::ConsentRequired => "consent_required",
109 Self::InvalidRequestUri => "invalid_request_uri",
110 Self::InvalidRequestObject => "invalid_request_object",
111 Self::RequestNotSupported => "request_not_supported",
112 Self::RequestUriNotSupported => "request_uri_not_supported",
113 Self::RegistrationNotSupported => "registration_not_supported",
114 Self::UnmetAuthenticationRequirements => "unmet_authentication_requirements",
115 Self::UnmetAuthenticationContextRequirements => "unmet_authentication_context_requirements",
116 Self::SessionSelectionRequired => "session_selection_required",
117 Self::AuthenticationMethodRequired => "authentication_method_required",
118 Self::InsufficientIdentityAssurance => "insufficient_identity_assurance",
119 Self::TemporarilyUnavailable => "temporarily_unavailable",
120 Self::RegistrationRequired => "registration_required",
121 Self::UnsupportedPromptValue => "unsupported_prompt_value",
122 Self::UserSelectionRequired => "user_selection_required",
123 };
124 f.write_str(s)
125 }
126}
127
128impl std::str::FromStr for OidcErrorCode {
129 type Err = AuthError;
130
131 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
132 match s {
133 "invalid_request" => Ok(Self::InvalidRequest),
134 "invalid_client" => Ok(Self::InvalidClient),
135 "invalid_grant" => Ok(Self::InvalidGrant),
136 "unauthorized_client" => Ok(Self::UnauthorizedClient),
137 "unsupported_grant_type" => Ok(Self::UnsupportedGrantType),
138 "invalid_scope" => Ok(Self::InvalidScope),
139 "interaction_required" => Ok(Self::InteractionRequired),
140 "login_required" => Ok(Self::LoginRequired),
141 "account_selection_required" => Ok(Self::AccountSelectionRequired),
142 "consent_required" => Ok(Self::ConsentRequired),
143 "invalid_request_uri" => Ok(Self::InvalidRequestUri),
144 "invalid_request_object" => Ok(Self::InvalidRequestObject),
145 "request_not_supported" => Ok(Self::RequestNotSupported),
146 "request_uri_not_supported" => Ok(Self::RequestUriNotSupported),
147 "registration_not_supported" => Ok(Self::RegistrationNotSupported),
148 "unmet_authentication_requirements" => Ok(Self::UnmetAuthenticationRequirements),
149 "unmet_authentication_context_requirements" => Ok(Self::UnmetAuthenticationContextRequirements),
150 "session_selection_required" => Ok(Self::SessionSelectionRequired),
151 "authentication_method_required" => Ok(Self::AuthenticationMethodRequired),
152 "insufficient_identity_assurance" => Ok(Self::InsufficientIdentityAssurance),
153 "temporarily_unavailable" => Ok(Self::TemporarilyUnavailable),
154 "registration_required" => Ok(Self::RegistrationRequired),
155 "unsupported_prompt_value" => Ok(Self::UnsupportedPromptValue),
156 "user_selection_required" => Ok(Self::UserSelectionRequired),
157 other => Err(AuthError::validation(format!("Unknown OIDC error code: {other}"))),
158 }
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct OidcErrorResponse {
165 pub error: OidcErrorCode,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub error_description: Option<String>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub error_uri: Option<String>,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub state: Option<String>,
176 #[serde(flatten)]
178 pub additional_details: HashMap<String, serde_json::Value>,
179}
180
181impl OidcErrorResponse {
182 pub fn new(error: OidcErrorCode) -> OidcErrorResponseBuilder {
195 OidcErrorResponseBuilder {
196 inner: OidcErrorResponse {
197 error,
198 error_description: None,
199 error_uri: None,
200 state: None,
201 additional_details: HashMap::new(),
202 },
203 }
204 }
205}
206
207pub struct OidcErrorResponseBuilder {
209 inner: OidcErrorResponse,
210}
211
212impl OidcErrorResponseBuilder {
213 pub fn description(mut self, desc: impl Into<String>) -> Self {
215 self.inner.error_description = Some(desc.into());
216 self
217 }
218
219 pub fn error_uri(mut self, uri: impl Into<String>) -> Self {
221 self.inner.error_uri = Some(uri.into());
222 self
223 }
224
225 pub fn state(mut self, state: impl Into<String>) -> Self {
227 self.inner.state = Some(state.into());
228 self
229 }
230
231 pub fn detail(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
233 self.inner.additional_details.insert(key.into(), value);
234 self
235 }
236
237 pub fn details(mut self, details: HashMap<String, serde_json::Value>) -> Self {
239 self.inner.additional_details = details;
240 self
241 }
242
243 pub fn build(self) -> OidcErrorResponse {
245 self.inner
246 }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct AuthenticationRequirements {
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub acr_values: Option<Vec<String>>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub amr_values: Option<Vec<String>>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub max_age: Option<u64>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub identity_assurance_level: Option<String>,
264}
265
266#[derive(Debug, Clone)]
268pub struct OidcErrorManager {
269 error_base_uri: String,
271 custom_error_mappings: HashMap<String, OidcErrorCode>,
273}
274
275impl Default for OidcErrorManager {
276 fn default() -> Self {
277 Self {
278 error_base_uri: "https://openid.net/specs/openid-connect-core-1_0.html#AuthError"
279 .to_string(),
280 custom_error_mappings: HashMap::new(),
281 }
282 }
283}
284
285impl OidcErrorCode {
286 pub fn get_description(&self) -> &'static str {
288 match self {
289 Self::InvalidRequest => {
290 "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed."
291 }
292 Self::InvalidClient => {
293 "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."
294 }
295 Self::InvalidGrant => {
296 "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
297 }
298 Self::UnauthorizedClient => {
299 "The authenticated client is not authorized to use this authorization grant type."
300 }
301 Self::UnsupportedGrantType => {
302 "The authorization grant type is not supported by the authorization server."
303 }
304 Self::InvalidScope => "The requested scope is invalid, unknown, or malformed.",
305
306 Self::InteractionRequired => {
307 "The authorization server requires end-user interaction of some form to proceed."
308 }
309 Self::LoginRequired => "The authorization server requires end-user authentication.",
310 Self::AccountSelectionRequired => {
311 "The end-user is required to select a session at the authorization server."
312 }
313 Self::ConsentRequired => "The authorization server requires end-user consent.",
314 Self::InvalidRequestUri => {
315 "The request_uri in the authorization request returns an error or contains invalid data."
316 }
317 Self::InvalidRequestObject => {
318 "The request parameter contains an invalid request object."
319 }
320 Self::RequestNotSupported => {
321 "The authorization server does not support use of the request parameter."
322 }
323 Self::RequestUriNotSupported => {
324 "The authorization server does not support use of the request_uri parameter."
325 }
326 Self::RegistrationNotSupported => {
327 "The authorization server does not support use of the registration parameter."
328 }
329
330 Self::UnmetAuthenticationRequirements => {
332 "The authentication performed does not meet the authentication requirements specified in the request."
333 }
334 Self::UnmetAuthenticationContextRequirements => {
335 "The requested authentication context class reference values were not satisfied by the performed authentication."
336 }
337 Self::SessionSelectionRequired => {
338 "Multiple active sessions exist, and the end-user must select which session to use."
339 }
340 Self::AuthenticationMethodRequired => {
341 "The authorization server requires the end-user to authenticate using a specific authentication method."
342 }
343 Self::InsufficientIdentityAssurance => {
344 "The level of identity assurance achieved does not meet the requirements for this request."
345 }
346 Self::TemporarilyUnavailable => {
347 "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server."
348 }
349 Self::RegistrationRequired => {
350 "The end-user must complete a registration process before authentication can proceed."
351 }
352 Self::UnsupportedPromptValue => {
353 "The authorization server does not support the requested prompt value."
354 }
355 Self::UserSelectionRequired => {
356 "Multiple users match the provided identification, and selection is required."
357 }
358 }
359 }
360
361 pub fn requires_interaction(&self) -> bool {
363 matches!(
364 self,
365 Self::InteractionRequired
366 | Self::LoginRequired
367 | Self::AccountSelectionRequired
368 | Self::ConsentRequired
369 | Self::SessionSelectionRequired
370 | Self::AuthenticationMethodRequired
371 | Self::RegistrationRequired
372 | Self::UserSelectionRequired
373 )
374 }
375
376 pub fn is_authentication_error(&self) -> bool {
378 matches!(
379 self,
380 Self::LoginRequired
381 | Self::UnmetAuthenticationRequirements
382 | Self::UnmetAuthenticationContextRequirements
383 | Self::AuthenticationMethodRequired
384 | Self::InsufficientIdentityAssurance
385 )
386 }
387}
388
389impl OidcErrorManager {
390 pub fn new(error_base_uri: String) -> Self {
392 Self {
393 error_base_uri,
394 custom_error_mappings: HashMap::new(),
395 }
396 }
397
398 pub fn create_unmet_auth_requirements_error(
400 &self,
401 requirements: AuthenticationRequirements,
402 state: Option<String>,
403 ) -> OidcErrorResponse {
404 let mut builder = OidcErrorResponse::new(OidcErrorCode::UnmetAuthenticationRequirements)
405 .description(
406 OidcErrorCode::UnmetAuthenticationRequirements
407 .get_description()
408 .to_string(),
409 )
410 .error_uri(format!(
411 "{}#UnmetAuthenticationRequirements",
412 self.error_base_uri
413 ));
414
415 if let Some(s) = state {
416 builder = builder.state(s);
417 }
418
419 if let Some(acr_values) = &requirements.acr_values {
420 builder = builder.detail(
421 "required_acr_values",
422 serde_json::to_value(acr_values).unwrap_or_default(),
423 );
424 }
425
426 if let Some(amr_values) = &requirements.amr_values {
427 builder = builder.detail(
428 "required_amr_values",
429 serde_json::to_value(amr_values).unwrap_or_default(),
430 );
431 }
432
433 if let Some(max_age) = requirements.max_age {
434 builder = builder.detail(
435 "max_age",
436 serde_json::Value::Number(serde_json::Number::from(max_age)),
437 );
438 }
439
440 builder.build()
441 }
442
443 pub fn create_insufficient_acr_error(
445 &self,
446 required_acr: Vec<String>,
447 achieved_acr: Option<String>,
448 state: Option<String>,
449 ) -> OidcErrorResponse {
450 let mut builder =
451 OidcErrorResponse::new(OidcErrorCode::UnmetAuthenticationContextRequirements)
452 .description(
453 OidcErrorCode::UnmetAuthenticationContextRequirements
454 .get_description()
455 .to_string(),
456 )
457 .error_uri(format!("{}#ACRRequirements", self.error_base_uri))
458 .detail(
459 "required_acr_values",
460 serde_json::to_value(required_acr).unwrap_or_default(),
461 );
462
463 if let Some(acr) = achieved_acr {
464 builder = builder.detail("achieved_acr", serde_json::Value::String(acr));
465 }
466
467 if let Some(s) = state {
468 builder = builder.state(s);
469 }
470
471 builder.build()
472 }
473
474 pub fn create_error_response(
476 &self,
477 error_code: OidcErrorCode,
478 custom_description: Option<String>,
479 state: Option<String>,
480 additional_details: HashMap<String, serde_json::Value>,
481 ) -> OidcErrorResponse {
482 let mut builder = OidcErrorResponse::new(error_code.clone())
483 .description(
484 custom_description.unwrap_or_else(|| error_code.get_description().to_string()),
485 )
486 .error_uri(format!("{}#{:?}", self.error_base_uri, error_code))
487 .details(additional_details);
488
489 if let Some(s) = state {
490 builder = builder.state(s);
491 }
492
493 builder.build()
494 }
495
496 pub fn add_custom_error_mapping(&mut self, identifier: String, error_code: OidcErrorCode) {
498 self.custom_error_mappings.insert(identifier, error_code);
499 }
500
501 pub fn remove_custom_error_mapping(&mut self, identifier: &str) -> Option<OidcErrorCode> {
503 self.custom_error_mappings.remove(identifier)
504 }
505
506 pub fn resolve_error_code(&self, identifier: &str) -> Option<OidcErrorCode> {
508 if let Some(error_code) = self.custom_error_mappings.get(identifier) {
510 return Some(error_code.clone());
511 }
512
513 identifier.parse::<OidcErrorCode>().ok()
515 }
516
517 pub fn create_error_response_from_identifier(
519 &self,
520 error_identifier: &str,
521 custom_description: Option<String>,
522 state: Option<String>,
523 additional_details: HashMap<String, serde_json::Value>,
524 ) -> Result<OidcErrorResponse> {
525 match self.resolve_error_code(error_identifier) {
526 Some(error_code) => Ok(self.create_error_response(
527 error_code,
528 custom_description,
529 state,
530 additional_details,
531 )),
532 None => Err(AuthError::validation(format!(
533 "Unknown error code identifier: {}",
534 error_identifier
535 ))),
536 }
537 }
538
539 pub fn get_custom_mappings(&self) -> &HashMap<String, OidcErrorCode> {
541 &self.custom_error_mappings
542 }
543
544 pub fn clear_custom_mappings(&mut self) {
546 self.custom_error_mappings.clear();
547 }
548
549 pub fn has_custom_mapping(&self, identifier: &str) -> bool {
551 self.custom_error_mappings.contains_key(identifier)
552 }
553
554 pub fn validate_authentication_requirements(
556 &self,
557 requirements: &AuthenticationRequirements,
558 performed_acr: Option<&str>,
559 performed_amr: Option<&[String]>,
560 auth_time: Option<u64>,
561 current_time: u64,
562 ) -> Result<()> {
563 if let Some(required_acr) = &requirements.acr_values {
565 match performed_acr {
566 Some(acr) => {
567 if !required_acr.contains(&acr.to_string()) {
568 return Err(AuthError::validation(
569 "Authentication context class requirements not met",
570 ));
571 }
572 }
573 None => {
574 return Err(AuthError::validation(
575 "No authentication context class provided",
576 ));
577 }
578 }
579 }
580
581 if let Some(required_amr) = &requirements.amr_values {
583 match performed_amr {
584 Some(amr) => {
585 for required in required_amr {
586 if !amr.contains(required) {
587 return Err(AuthError::validation(
588 "Authentication method requirements not met",
589 ));
590 }
591 }
592 }
593 None => {
594 return Err(AuthError::validation("No authentication methods provided"));
595 }
596 }
597 }
598
599 if let Some(max_age) = requirements.max_age {
601 if let Some(auth_time) = auth_time {
602 if current_time - auth_time > max_age {
603 return Err(AuthError::validation(
604 "Authentication is too old (exceeds max_age)",
605 ));
606 }
607 } else {
608 return Err(AuthError::validation(
609 "Authentication time not available for max_age validation",
610 ));
611 }
612 }
613
614 Ok(())
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[test]
623 fn test_error_code_descriptions() {
624 assert!(
625 !OidcErrorCode::UnmetAuthenticationRequirements
626 .get_description()
627 .is_empty()
628 );
629 assert!(OidcErrorCode::LoginRequired.requires_interaction());
630 assert!(OidcErrorCode::UnmetAuthenticationRequirements.is_authentication_error());
631 }
632
633 #[test]
634 fn test_oidc_error_code_display_roundtrip() {
635 let codes = vec![
637 OidcErrorCode::InvalidRequest,
638 OidcErrorCode::InvalidClient,
639 OidcErrorCode::InvalidGrant,
640 OidcErrorCode::UnauthorizedClient,
641 OidcErrorCode::UnsupportedGrantType,
642 OidcErrorCode::InvalidScope,
643 OidcErrorCode::InteractionRequired,
644 OidcErrorCode::LoginRequired,
645 OidcErrorCode::AccountSelectionRequired,
646 OidcErrorCode::ConsentRequired,
647 OidcErrorCode::InvalidRequestUri,
648 OidcErrorCode::InvalidRequestObject,
649 OidcErrorCode::RequestNotSupported,
650 OidcErrorCode::RequestUriNotSupported,
651 OidcErrorCode::RegistrationNotSupported,
652 OidcErrorCode::UnmetAuthenticationRequirements,
653 OidcErrorCode::UnmetAuthenticationContextRequirements,
654 OidcErrorCode::SessionSelectionRequired,
655 OidcErrorCode::AuthenticationMethodRequired,
656 OidcErrorCode::InsufficientIdentityAssurance,
657 OidcErrorCode::TemporarilyUnavailable,
658 OidcErrorCode::RegistrationRequired,
659 OidcErrorCode::UnsupportedPromptValue,
660 OidcErrorCode::UserSelectionRequired,
661 ];
662
663 for code in codes {
664 let s = code.to_string();
665 let parsed: OidcErrorCode = s.parse().unwrap();
666 assert_eq!(parsed, code);
667 }
668 }
669
670 #[test]
671 fn test_oidc_error_code_from_str_invalid() {
672 let result = "not_a_real_error".parse::<OidcErrorCode>();
673 assert!(result.is_err());
674 }
675
676 #[test]
677 fn test_oidc_error_response_builder() {
678 let resp = OidcErrorResponse::new(OidcErrorCode::LoginRequired)
679 .description("Session expired")
680 .error_uri("https://example.com/errors#login")
681 .state("abc123")
682 .detail("session_id", serde_json::json!("sess-42"))
683 .build();
684
685 assert_eq!(resp.error, OidcErrorCode::LoginRequired);
686 assert_eq!(resp.error_description.as_deref(), Some("Session expired"));
687 assert_eq!(
688 resp.error_uri.as_deref(),
689 Some("https://example.com/errors#login")
690 );
691 assert_eq!(resp.state.as_deref(), Some("abc123"));
692 assert_eq!(
693 resp.additional_details.get("session_id"),
694 Some(&serde_json::json!("sess-42"))
695 );
696 }
697
698 #[test]
699 fn test_unmet_auth_requirements_error() {
700 let manager = OidcErrorManager::default();
701 let requirements = AuthenticationRequirements {
702 acr_values: Some(vec!["urn:mace:incommon:iap:silver".to_string()]),
703 amr_values: Some(vec!["pwd".to_string(), "mfa".to_string()]),
704 max_age: Some(3600),
705 identity_assurance_level: None,
706 };
707
708 let error = manager
709 .create_unmet_auth_requirements_error(requirements, Some("state123".to_string()));
710
711 assert_eq!(error.error, OidcErrorCode::UnmetAuthenticationRequirements);
712 assert!(error.error_description.is_some());
713 assert_eq!(error.state.as_ref().unwrap(), "state123");
714 assert!(error.additional_details.contains_key("required_acr_values"));
715 assert!(error.additional_details.contains_key("required_amr_values"));
716 }
717
718 #[test]
719 fn test_custom_error_mappings() {
720 let mut manager = OidcErrorManager::default();
721
722 manager.add_custom_error_mapping(
724 "custom_validation_failed".to_string(),
725 OidcErrorCode::InvalidRequest,
726 );
727
728 let resolved = manager.resolve_error_code("custom_validation_failed");
730 assert_eq!(resolved, Some(OidcErrorCode::InvalidRequest));
731
732 let standard = manager.resolve_error_code("login_required");
734 assert_eq!(standard, Some(OidcErrorCode::LoginRequired));
735
736 let unknown = manager.resolve_error_code("nonexistent_error");
738 assert_eq!(unknown, None);
739
740 assert!(manager.has_custom_mapping("custom_validation_failed"));
742 assert!(!manager.has_custom_mapping("login_required"));
743
744 let error_response = manager
746 .create_error_response_from_identifier(
747 "custom_validation_failed",
748 Some("Custom validation error".to_string()),
749 Some("state123".to_string()),
750 HashMap::new(),
751 )
752 .unwrap();
753
754 assert_eq!(error_response.error, OidcErrorCode::InvalidRequest);
755 assert_eq!(error_response.state.as_ref().unwrap(), "state123");
756
757 let removed = manager.remove_custom_error_mapping("custom_validation_failed");
759 assert_eq!(removed, Some(OidcErrorCode::InvalidRequest));
760 assert!(!manager.has_custom_mapping("custom_validation_failed"));
761
762 manager.add_custom_error_mapping("test1".to_string(), OidcErrorCode::InvalidScope);
764 manager.add_custom_error_mapping("test2".to_string(), OidcErrorCode::ConsentRequired);
765 assert_eq!(manager.get_custom_mappings().len(), 2);
766
767 manager.clear_custom_mappings();
768 assert_eq!(manager.get_custom_mappings().len(), 0);
769 }
770
771 #[test]
772 fn test_error_response_from_unknown_identifier() {
773 let manager = OidcErrorManager::default();
774
775 let result = manager.create_error_response_from_identifier(
776 "unknown_error_code",
777 None,
778 None,
779 HashMap::new(),
780 );
781
782 assert!(result.is_err());
783 assert!(
784 result
785 .unwrap_err()
786 .to_string()
787 .contains("Unknown error code identifier")
788 );
789 }
790
791 #[test]
792 fn test_custom_error_mappings_real_world_scenario() {
793 let mut manager = OidcErrorManager::default();
794
795 manager.add_custom_error_mapping(
797 "account_frozen".to_string(),
798 OidcErrorCode::AuthenticationMethodRequired,
799 );
800 manager.add_custom_error_mapping(
801 "kyc_verification_required".to_string(),
802 OidcErrorCode::InsufficientIdentityAssurance,
803 );
804 manager.add_custom_error_mapping(
805 "payment_limit_exceeded".to_string(),
806 OidcErrorCode::ConsentRequired,
807 );
808
809 let mut additional_details = HashMap::new();
811 additional_details.insert(
812 "account_id".to_string(),
813 serde_json::Value::String("acc-12345".to_string()),
814 );
815 additional_details.insert(
816 "freeze_reason".to_string(),
817 serde_json::Value::String("Suspicious activity detected".to_string()),
818 );
819
820 let error_response = manager
821 .create_error_response_from_identifier(
822 "account_frozen",
823 Some("Account authentication required due to security freeze".to_string()),
824 Some("banking-session-456".to_string()),
825 additional_details,
826 )
827 .unwrap();
828
829 assert_eq!(
830 error_response.error,
831 OidcErrorCode::AuthenticationMethodRequired
832 );
833 assert_eq!(
834 error_response.error_description.as_ref().unwrap(),
835 "Account authentication required due to security freeze"
836 );
837 assert_eq!(
838 error_response.state.as_ref().unwrap(),
839 "banking-session-456"
840 );
841 assert!(error_response.additional_details.contains_key("account_id"));
842 assert!(
843 error_response
844 .additional_details
845 .contains_key("freeze_reason")
846 );
847
848 manager.add_custom_error_mapping(
850 "login_required".to_string(),
851 OidcErrorCode::RegistrationRequired, );
853
854 let overridden_response = manager
855 .create_error_response_from_identifier(
856 "login_required",
857 Some("User registration required before login".to_string()),
858 None,
859 HashMap::new(),
860 )
861 .unwrap();
862
863 assert_eq!(
864 overridden_response.error,
865 OidcErrorCode::RegistrationRequired
866 );
867
868 assert_eq!(manager.get_custom_mappings().len(), 4);
870 assert!(manager.has_custom_mapping("account_frozen"));
871 assert!(!manager.has_custom_mapping("nonexistent_mapping"));
872
873 let removed = manager.remove_custom_error_mapping("account_frozen");
875 assert_eq!(removed, Some(OidcErrorCode::AuthenticationMethodRequired));
876 assert!(!manager.has_custom_mapping("account_frozen"));
877
878 manager.clear_custom_mappings();
880 assert_eq!(manager.get_custom_mappings().len(), 0);
881 }
882
883 #[test]
884 fn test_standard_error_code_resolution() {
885 let manager = OidcErrorManager::default();
886
887 assert_eq!(
889 manager.resolve_error_code("invalid_request"),
890 Some(OidcErrorCode::InvalidRequest)
891 );
892 assert_eq!(
893 manager.resolve_error_code("unmet_authentication_requirements"),
894 Some(OidcErrorCode::UnmetAuthenticationRequirements)
895 );
896 assert_eq!(
897 manager.resolve_error_code("session_selection_required"),
898 Some(OidcErrorCode::SessionSelectionRequired)
899 );
900
901 let mut manager = OidcErrorManager::default();
903 manager.add_custom_error_mapping(
904 "login_required".to_string(),
905 OidcErrorCode::ConsentRequired, );
907
908 assert_eq!(
909 manager.resolve_error_code("login_required"),
910 Some(OidcErrorCode::ConsentRequired) );
912 }
913}