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}