Skip to main content

rustauth_oauth/oauth2/
validate_authorization_code.rs

1use std::collections::BTreeMap;
2
3use super::error::OAuthError;
4use super::request::{
5    apply_client_authentication, is_protected_oauth_param, ClientAuthentication, OAuthFormRequest,
6};
7use super::tokens::ProviderOptions;
8use super::utils::validate_code_verifier;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct AuthorizationCodeRequest {
12    pub code: String,
13    pub redirect_uri: String,
14    pub options: ProviderOptions,
15    pub code_verifier: Option<String>,
16    pub device_id: Option<String>,
17    pub authentication: ClientAuthentication,
18    pub headers: BTreeMap<String, String>,
19    pub additional_params: BTreeMap<String, String>,
20    pub override_params: BTreeMap<String, String>,
21    pub resource: Vec<String>,
22}
23
24impl Default for AuthorizationCodeRequest {
25    fn default() -> Self {
26        Self {
27            code: String::new(),
28            redirect_uri: String::new(),
29            options: ProviderOptions::default(),
30            code_verifier: None,
31            device_id: None,
32            authentication: ClientAuthentication::Post,
33            headers: BTreeMap::new(),
34            additional_params: BTreeMap::new(),
35            override_params: BTreeMap::new(),
36            resource: Vec::new(),
37        }
38    }
39}
40
41impl AuthorizationCodeRequest {
42    pub fn try_new(
43        code: impl Into<String>,
44        redirect_uri: impl Into<String>,
45        options: ProviderOptions,
46    ) -> Result<Self, OAuthError> {
47        let code = code.into();
48        if code.is_empty() {
49            return Err(OAuthError::InvalidConfiguration(
50                "authorization code cannot be empty".to_owned(),
51            ));
52        }
53        let redirect_uri = redirect_uri.into();
54        url::Url::parse(options.redirect_uri.as_deref().unwrap_or(&redirect_uri))?;
55        Ok(Self {
56            code,
57            redirect_uri,
58            options,
59            ..Self::default()
60        })
61    }
62
63    pub fn code_verifier(mut self, code_verifier: impl Into<String>) -> Self {
64        self.code_verifier = Some(code_verifier.into());
65        self
66    }
67
68    pub fn authentication(mut self, authentication: ClientAuthentication) -> Self {
69        self.authentication = authentication;
70        self
71    }
72
73    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
74        self.headers.insert(key.into(), value.into());
75        self
76    }
77
78    /// Adds a non-sensitive extension form field if not already set.
79    /// Security-critical keys (`state`, `redirect_uri`, PKCE, `grant_type`,
80    /// `code`, and client credential fields) are ignored.
81    pub fn additional_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
82        self.additional_params.insert(key.into(), value.into());
83        self
84    }
85
86    /// Overrides a non-sensitive form field. Security-critical keys (`state`,
87    /// `redirect_uri`, PKCE, `grant_type`, `code`, and client credential
88    /// fields) are ignored so validated flow invariants and client credentials
89    /// cannot be replaced after authentication is applied.
90    pub fn override_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
91        self.override_params.insert(key.into(), value.into());
92        self
93    }
94
95    pub fn resource(mut self, resource: impl Into<String>) -> Self {
96        self.resource.push(resource.into());
97        self
98    }
99}
100
101pub fn create_authorization_code_request(
102    input: AuthorizationCodeRequest,
103) -> Result<OAuthFormRequest, OAuthError> {
104    validate_authorization_code_request(&input)?;
105    let mut request = OAuthFormRequest::new();
106    for (key, value) in input.headers {
107        request.set_header(key, value);
108    }
109    request.set_body("grant_type", "authorization_code");
110    request.set_body("code", input.code);
111    if let Some(code_verifier) = input.code_verifier {
112        request.set_body("code_verifier", code_verifier);
113    }
114    if let Some(client_key) = &input.options.client_key {
115        request.set_body("client_key", client_key);
116    }
117    if let Some(device_id) = input.device_id {
118        request.set_body("device_id", device_id);
119    }
120    request.set_body(
121        "redirect_uri",
122        input
123            .options
124            .redirect_uri
125            .as_deref()
126            .unwrap_or(&input.redirect_uri),
127    );
128    for resource in input.resource {
129        request.push_body("resource", resource);
130    }
131    apply_client_authentication(&mut request, &input.options, input.authentication, false)?;
132    for (key, value) in input.additional_params {
133        if is_protected_oauth_param(&key) || request.has_body(&key) {
134            continue;
135        }
136        request.push_body(key, value);
137    }
138    for (key, value) in input.override_params {
139        if is_protected_oauth_param(&key) {
140            continue;
141        }
142        request.set_body(key, value);
143    }
144    Ok(request)
145}
146
147fn validate_authorization_code_request(input: &AuthorizationCodeRequest) -> Result<(), OAuthError> {
148    if input.code.is_empty() {
149        return Err(OAuthError::InvalidConfiguration(
150            "authorization code cannot be empty".to_owned(),
151        ));
152    }
153    let redirect_uri = input
154        .options
155        .redirect_uri
156        .as_deref()
157        .unwrap_or(&input.redirect_uri);
158    url::Url::parse(redirect_uri)?;
159    if let Some(code_verifier) = &input.code_verifier {
160        validate_code_verifier(code_verifier)?;
161    }
162    Ok(())
163}