haste_server/auth_n/oidc/error.rs
1// Custom OIDC error types
2// Based on https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 and https://openid.net/specs/openid-connect-core-1_0.html and 3.1.2.6
3
4use axum::response::{IntoResponse, Redirect};
5
6#[derive(serde::Serialize, Debug)]
7pub enum OIDCErrorCode {
8 /**
9 * The request is missing a required parameter, includes aninvalid parameter value,
10 * includes a parameter more than once, or is otherwise malformed.
11 */
12 #[serde(rename = "invalid_request")]
13 InvalidRequest,
14 /**
15 * The client is not authorized to request an authorization
16 * code using this method.
17 */
18 #[serde(rename = "unauthorized_client")]
19 UnauthorizedClient,
20 /**
21 * The authorization server does not support obtaining an
22 * authorization code using this method.
23 */
24 #[serde(rename = "unsupported_response_type")]
25 UnsupportedResponseType,
26 /**
27 * The requested scope is invalid, unknown, or malformed.
28 */
29 #[serde(rename = "invalid_scope")]
30 InvalidScope,
31 /**
32 * The authorization server encountered an unexpected
33 * condition that prevented it from fulfilling the request.
34 * (This error code is needed because a 500 Internal Server
35 * Error HTTP status code cannot be returned to the client
36 * via an HTTP redirect.)
37 */
38 #[serde(rename = "server_error")]
39 ServerError,
40 /**
41 * The authorization server is currently unable to handle
42 * the request due to a temporary overloading or maintenance
43 * of the server. (This error code is needed because a 503
44 * Service Unavailable HTTP status code cannot be returned
45 * to the client via an HTTP redirect.)
46 */
47 #[serde(rename = "temporarily_unavailable")]
48 TemporarilyUnavailable,
49 /**
50 * Client authentication failed (e.g., unknown client, no
51 * client authentication included, or unsupported
52 * authentication method). The authorization server MAY
53 * return an HTTP 401 (Unauthorized) status code to indicate
54 * which HTTP authentication schemes are supported. If the
55 * client attempted to authenticate via the "Authorization"
56 * request header field, the authorization server MUST
57 * respond with an HTTP 401 (Unauthorized) status code and
58 * include the "WWW-Authenticate" response header field
59 * matching the authentication scheme used by the client.
60 */
61 #[serde(rename = "invalid_client")]
62 InvalidClient,
63 /**
64 * The provided authorization grant (e.g., authorization
65 * code, resource owner credentials) or refresh token is
66 * invalid, expired, revoked, does not match the redirection
67 * URI used in the authorization request, or was issued to
68 * another client.
69 */
70 #[serde(rename = "invalid_grant")]
71 InvalidGrant,
72
73 #[serde(rename = "access_denied")]
74 AccessDenied,
75}
76
77impl From<&OIDCErrorCode> for &str {
78 fn from(code: &OIDCErrorCode) -> Self {
79 match code {
80 OIDCErrorCode::InvalidRequest => "invalid_request",
81 OIDCErrorCode::UnauthorizedClient => "unauthorized_client",
82 OIDCErrorCode::UnsupportedResponseType => "unsupported_response_type",
83 OIDCErrorCode::InvalidScope => "invalid_scope",
84 OIDCErrorCode::ServerError => "server_error",
85 OIDCErrorCode::TemporarilyUnavailable => "temporarily_unavailable",
86 OIDCErrorCode::InvalidClient => "invalid_client",
87 OIDCErrorCode::InvalidGrant => "invalid_grant",
88 OIDCErrorCode::AccessDenied => "access_denied",
89 }
90 }
91}
92
93#[allow(dead_code)]
94#[derive(serde::Serialize, Debug)]
95pub struct OIDCError {
96 pub code: OIDCErrorCode,
97 pub description: Option<String>,
98 #[serde(skip_serializing)]
99 pub redirect_uri: Option<String>,
100}
101
102impl OIDCError {
103 pub fn new(
104 code: OIDCErrorCode,
105 description: Option<String>,
106 redirect_uri: Option<String>,
107 ) -> Self {
108 OIDCError {
109 code,
110 description,
111 redirect_uri,
112 }
113 }
114}
115
116impl IntoResponse for OIDCError {
117 fn into_response(self) -> axum::response::Response {
118 let error_code: &str = (&self.code).into();
119
120 if let Some(error_uri) = self.redirect_uri {
121 let mut redirect_uri = error_uri + "?error=" + error_code;
122
123 if let Some(description) = self.description {
124 redirect_uri = redirect_uri + "&error_description=" + &description;
125 }
126
127 Redirect::to(&redirect_uri).into_response()
128 } else {
129 let json_body = serde_json::to_string(&self).unwrap_or_default();
130 (axum::http::StatusCode::BAD_REQUEST, json_body).into_response()
131 }
132 }
133}