securitydept_oidc_client/
error.rs1use http::StatusCode;
2use securitydept_oauth_provider::OAuthProviderError;
3use securitydept_utils::{
4 error::{ErrorPresentation, ToErrorPresentation, UserRecovery},
5 http::ToHttpStatus,
6};
7use snafu::Snafu;
8
9#[derive(Debug, Snafu)]
10#[snafu(visibility(pub))]
11pub enum OidcError {
12 #[snafu(display("OIDC metadata error: {message}"))]
13 Metadata { message: String },
14
15 #[snafu(display("OIDC token exchange error: {message}"))]
16 TokenExchange { message: String },
17
18 #[snafu(display("OIDC device authorization error: {message}"))]
19 DeviceAuthorization { message: String },
20
21 #[snafu(display("OIDC device token poll error: {message}"))]
22 DeviceTokenPoll { message: String },
23
24 #[snafu(display("OIDC redirect URL error: {source}"))]
25 RedirectUrl { source: url::ParseError },
26
27 #[snafu(display("OIDC claims error: {message}"))]
28 Claims { message: String },
29
30 #[snafu(display("OIDC claims check script compile error: {message}"))]
31 ClaimsCheckScriptCompile { message: String },
32
33 #[snafu(display("OIDC claims check reject error: {message}"))]
34 ClaimsCheckReject { message: String },
35
36 #[snafu(display("OIDC invalid configuration: {message}"))]
37 InvalidConfig { message: String },
38
39 #[snafu(display("OIDC CSRF validation error: {message}"))]
40 CSRFValidation { message: String },
41
42 #[snafu(display("OIDC pending OAuth error: {source}"))]
43 PendingOauth {
44 source: Box<dyn std::error::Error + Send + Sync>,
45 },
46
47 #[snafu(display("OIDC token refresh error: {message}"))]
48 TokenRefresh { message: String },
49
50 #[snafu(display("OIDC token revocation error: {message}"))]
51 TokenRevocation { message: String },
52
53 #[snafu(display("OIDC scope validation error: token is missing required scopes: {missing:?}"))]
56 ScopeValidation { missing: Vec<String> },
57}
58
59impl ToHttpStatus for OidcError {
60 fn to_http_status(&self) -> StatusCode {
61 match self {
62 OidcError::ClaimsCheckReject { .. }
63 | OidcError::CSRFValidation { .. }
64 | OidcError::Claims { .. }
65 | OidcError::PendingOauth { .. } => StatusCode::UNAUTHORIZED,
66 OidcError::InvalidConfig { .. }
67 | OidcError::Metadata { .. }
68 | OidcError::TokenExchange { .. }
69 | OidcError::DeviceAuthorization { .. }
70 | OidcError::DeviceTokenPoll { .. }
71 | OidcError::RedirectUrl { .. }
72 | OidcError::TokenRefresh { .. }
73 | OidcError::TokenRevocation { .. }
74 | OidcError::ClaimsCheckScriptCompile { .. } => StatusCode::INTERNAL_SERVER_ERROR,
75 OidcError::ScopeValidation { .. } => StatusCode::FORBIDDEN,
76 }
77 }
78}
79
80pub type OidcResult<T> = std::result::Result<T, OidcError>;
81
82impl From<OAuthProviderError> for OidcError {
83 fn from(value: OAuthProviderError) -> Self {
84 match value {
85 OAuthProviderError::InvalidConfig { message } => Self::InvalidConfig { message },
86 OAuthProviderError::Metadata { message } => Self::Metadata { message },
87 OAuthProviderError::HttpClient { message } => Self::Metadata { message },
88 OAuthProviderError::Introspection { message } => Self::TokenExchange { message },
89 }
90 }
91}
92
93impl ToErrorPresentation for OidcError {
94 fn to_error_presentation(&self) -> ErrorPresentation {
95 match self {
96 OidcError::RedirectUrl { .. } => ErrorPresentation::new(
97 "oidc_redirect_url_invalid",
98 "The login redirect URL is invalid.",
99 UserRecovery::ContactSupport,
100 ),
101 OidcError::CSRFValidation { .. } => ErrorPresentation::new(
102 "oidc_request_invalid",
103 "The sign-in request is no longer valid. Start again.",
104 UserRecovery::RestartFlow,
105 ),
106 OidcError::PendingOauth { .. } => ErrorPresentation::new(
107 "oidc_request_expired",
108 "The sign-in request expired or was already used. Start again.",
109 UserRecovery::RestartFlow,
110 ),
111 OidcError::ClaimsCheckReject { .. } => ErrorPresentation::new(
112 "oidc_access_denied",
113 "Your account is not allowed to sign in.",
114 UserRecovery::ContactSupport,
115 ),
116 OidcError::DeviceAuthorization { .. } => ErrorPresentation::new(
117 "oidc_device_authorization_failed",
118 "The device sign-in could not be started. Try again.",
119 UserRecovery::Retry,
120 ),
121 OidcError::DeviceTokenPoll { .. } => ErrorPresentation::new(
122 "oidc_device_sign_in_failed",
123 "The device sign-in could not be completed. Start again.",
124 UserRecovery::RestartFlow,
125 ),
126 OidcError::TokenRefresh { .. } => ErrorPresentation::new(
127 "oidc_reauthentication_required",
128 "Your sign-in session expired. Sign in again.",
129 UserRecovery::Reauthenticate,
130 ),
131 OidcError::TokenRevocation { .. } => ErrorPresentation::new(
132 "oidc_token_revocation_failed",
133 "The token could not be revoked. Try again.",
134 UserRecovery::Retry,
135 ),
136 OidcError::Metadata { .. } | OidcError::TokenExchange { .. } => ErrorPresentation::new(
137 "oidc_sign_in_failed",
138 "The sign-in could not be completed. Start again.",
139 UserRecovery::RestartFlow,
140 ),
141 OidcError::Claims { .. } => ErrorPresentation::new(
142 "oidc_invalid_response",
143 "The sign-in response was invalid. Start again.",
144 UserRecovery::RestartFlow,
145 ),
146 OidcError::ClaimsCheckScriptCompile { .. } | OidcError::InvalidConfig { .. } => {
147 ErrorPresentation::new(
148 "oidc_temporarily_unavailable",
149 "Authentication is temporarily unavailable.",
150 UserRecovery::ContactSupport,
151 )
152 }
153 OidcError::ScopeValidation { .. } => ErrorPresentation::new(
154 "oidc_insufficient_scope",
155 "The issued token does not grant the required permissions.",
156 UserRecovery::ContactSupport,
157 ),
158 }
159 }
160}