raos/authorize/
request.rs

1use crate::common::{
2    frontend::{FrontendRequest, FrontendRequestMethod, OAuthValidationError},
3    model::CodeChallenge,
4    syntax::{ValidateSyntax, CLIENT_ID_SYNTAX, STATE_SYNTAX},
5    util::NoneIfEmpty,
6};
7
8/// The response type expected in an authorization request.
9#[derive(Debug, PartialEq)]
10pub enum ResponseType {
11    /// The client is requesting an authorization code.
12    Code,
13}
14
15/// A parsed authorization request from a client.
16/// This struct contains all the information needed to authorize a client's request.
17/// This is produced by parsing a [FrontendRequest] from a client.
18#[derive(Debug)]
19pub struct AuthorizationRequest {
20    /// The response type expected in the request.
21    pub response_type: ResponseType,
22    /// The client ID of the client making the request.
23    pub client_id: String,
24    /// The code challenge and method used in the request.
25    pub code_challenge: CodeChallenge,
26    /// The redirect URI the client expects to be redirected to.
27    pub redirect_uri: Option<String>,
28    /// The scope of the request, space separated
29    pub scope: Option<String>,
30    /// The state of the request to be sent back to the client in the response.
31    pub state: Option<String>,
32}
33
34impl TryFrom<&dyn FrontendRequest> for AuthorizationRequest {
35    type Error = OAuthValidationError;
36
37    fn try_from(request: &dyn FrontendRequest) -> Result<Self, Self::Error> {
38        if !matches!(
39            request.request_method(),
40            FrontendRequestMethod::GET | FrontendRequestMethod::POST
41        ) {
42            return Err(OAuthValidationError::InvalidRequestMethod {
43                expected: FrontendRequestMethod::GET,
44                actual: request.request_method(),
45            });
46        }
47
48        // Helper function to get a parameter from either the query or body if it's a POST request
49        let param = |key| {
50            request.query_param(key).none_if_empty().or_else(|| {
51                if let FrontendRequestMethod::POST = request.request_method() {
52                    request.body_param(key).none_if_empty()
53                } else {
54                    None
55                }
56            })
57        };
58
59        // Get the response type, client ID, and code challenge method from the request
60        let response_type = match param("response_type") {
61            Some(str) => str.try_into()?,
62            None => return Err(OAuthValidationError::MissingRequiredParameter("response_type")),
63        };
64        let Some(client_id) = param("client_id") else {
65            return Err(OAuthValidationError::MissingRequiredParameter("client_id"));
66        };
67        client_id.validate_syntax("client_id", &CLIENT_ID_SYNTAX)?;
68        let code_challenge =
69            (param("code_challenge"), param("code_challenge_method")).try_into()?;
70
71        let state = param("state");
72        state.validate_syntax("state", &STATE_SYNTAX)?;
73
74        // Return the authorization request
75        Ok(Self {
76            response_type,
77            client_id,
78            code_challenge,
79            state,
80            redirect_uri: param("redirect_uri"),
81            scope: param("scope"),
82        })
83    }
84}
85
86impl TryFrom<String> for ResponseType {
87    type Error = OAuthValidationError;
88
89    fn try_from(value: String) -> Result<Self, Self::Error> {
90        // Match the response type string to an enum variant
91        Ok(match value.to_lowercase().as_str() {
92            "code" => Self::Code,
93            _ => {
94                return Err(OAuthValidationError::InvalidParameterValue(
95                    "response_type",
96                    value.to_string(),
97                ))
98            }
99        })
100    }
101}
102
103impl TryFrom<(Option<String>, Option<String>)> for CodeChallenge {
104    type Error = OAuthValidationError;
105
106    fn try_from(
107        (code_challenge, code_challenge_method): (Option<String>, Option<String>),
108    ) -> Result<Self, Self::Error> {
109        let Some(code_challenge) = code_challenge else {
110            return Ok(Self::None);
111        };
112        let Some(method) = code_challenge_method else {
113            return Ok(Self::Plain { code_challenge });
114        };
115
116        // Match the code challenge method string to an enum variant
117        Ok(match method.to_lowercase().as_str() {
118            "plain" => Self::Plain { code_challenge },
119            "s256" => Self::S256 { code_challenge },
120            _ => {
121                return Err(OAuthValidationError::InvalidParameterValue(
122                    "code_challenge_method",
123                    method,
124                ))
125            }
126        })
127    }
128}