oauth2_reqwest/lib.rs
1#![warn(missing_docs)]
2//! A [`reqwest`] client for [`oauth2`].
3//!
4//! # Motivation
5//!
6//! The `reqwest` client [bundled](https://docs.rs/oauth2/latest/oauth2/#http-clients) with `oauth2`
7//! supports `reqwest` version 0.12. This separate crate supports `reqwest` version 0.13 and is
8//! intended to support future versions of `reqwest` without needing a new SemVer major version
9//! number for `oauth2` (which would otherwise be required due to breaking changes to that crate's
10//! public API).
11//!
12//! # Usage
13//!
14//! To get started, add the following dependencies to your crate's `Cargo.toml`:
15//! ```toml
16//! # Disables oauth2's default reqwest 0.12 client.
17//! oauth2 = { version = "5", default-features = false }
18//!
19//! # Imports reqwest without any feature flags enabled.
20#![doc = concat!("oauth2-reqwest = \"", env!("CARGO_PKG_VERSION"), "\"")]
21//!
22//! # Enables reqwest's default features.
23//! reqwest = "0.13"
24//! # Alternatively, specify the desired set of features:
25//! # reqwest = { version = "0.13", default-features = false, features = ["native-tls"] }
26//! ```
27//!
28//! For flexibility, this crate disables all of `reqwest`'s Cargo feature flags by default. To
29//! enable specific `reqwest` features (including its default `rustls` feature), separately import
30//! `reqwest` in your crate's `Cargo.toml` and specify the
31//! [desired features](https://docs.rs/crate/reqwest/latest/features). This approach leverages Cargo
32//! [feature unification](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification).
33//! While this approach requires a separate import, it provides maximum flexibility and reduces the
34//! need for future breaking changes to this crate.
35//!
36//! ## Asynchronous Client
37//!
38//! To use the async `reqwest` client, simply wrap the `reqwest` [`Client`](reqwest::Client) with
39//! this crate's [`ReqwestClient`] and pass the `ReqwestClient` to the desired `request_async`
40//! method:
41//!
42//! ```rust,no_run
43//! use oauth2_reqwest::ReqwestClient;
44//!
45//! # async fn err_wrapper() -> Result<(), anyhow::Error> {
46//! # let client = oauth2::basic::BasicClient::new(oauth2::ClientId::new("client_id".to_string()))
47//! # .set_token_uri(oauth2::TokenUrl::new("http://token".to_string())?);
48//! let reqwest_client = reqwest::ClientBuilder::new()
49//! // Following redirects opens the client up to SSRF vulnerabilities.
50//! .redirect(reqwest::redirect::Policy::none())
51//! .build()
52//! .expect("Client should build");
53//! let http_client = ReqwestClient::from(reqwest_client);
54//!
55//! # let code = oauth2::AuthorizationCode::new("code".to_string());
56//! // This code assumes `client` is a previously constructed `oauth2::Client` and `code` is an
57//! // `oauth2::AuthorizationCode`.
58//! let token_result = client
59//! .exchange_code(code)
60//! .request_async(&http_client)
61//! .await?;
62//!
63//! # Ok(())
64//! # }
65//! ```
66//!
67//! ## Synchronous Client
68//!
69//! To use the blocking `reqwest` client, first enable the `blocking` feature in `Cargo.toml`:
70//! ```toml
71#![doc = concat!("oauth2-reqwest = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"blocking\"] }")]
72//! ```
73//!
74//! Then, simply wrap the `reqwest` blocking [`Client`](reqwest::blocking::Client) with
75//! this crate's [`ReqwestBlockingClient`] and pass the `ReqwestBlockingClient` to the desired
76//! `request` method:
77//!
78//! ```rust,no_run
79//! # #[cfg(feature = "blocking")]
80//! use oauth2_reqwest::ReqwestBlockingClient;
81//!
82//! # #[cfg(feature = "blocking")]
83//! # fn err_wrapper() -> Result<(), anyhow::Error> {
84//! # let client = oauth2::basic::BasicClient::new(oauth2::ClientId::new("client_id".to_string()))
85//! # .set_token_uri(oauth2::TokenUrl::new("http://token".to_string())?);
86//! let reqwest_client = reqwest::blocking::ClientBuilder::new()
87//! // Following redirects opens the client up to SSRF vulnerabilities.
88//! .redirect(reqwest::redirect::Policy::none())
89//! .build()
90//! .expect("Client should build");
91//! let http_client = ReqwestBlockingClient::from(reqwest_client);
92//!
93//! # let code = oauth2::AuthorizationCode::new("code".to_string());
94//! // This code assumes `client` is a previously constructed `oauth2::Client` and `code` is an
95//! // `oauth2::AuthorizationCode`.
96//! let token_result = client
97//! .exchange_code(code)
98//! .request(&http_client)?;
99//!
100//! # Ok(())
101//! # }
102//! ```
103use oauth2::{http, AsyncHttpClient, HttpClientError, HttpRequest, HttpResponse};
104use std::future::Future;
105use std::pin::Pin;
106
107/// Asynchronous `reqwest` [`Client`](reqwest::Client) wrapper.
108///
109/// See the [crate-level documentation](crate) for usage instructions.
110#[derive(Clone, Debug, Default)]
111pub struct ReqwestClient(reqwest::Client);
112
113impl From<reqwest::Client> for ReqwestClient {
114 fn from(inner: reqwest::Client) -> Self {
115 Self(inner)
116 }
117}
118
119impl<'c> AsyncHttpClient<'c> for ReqwestClient {
120 type Error = HttpClientError<reqwest::Error>;
121
122 #[cfg(target_arch = "wasm32")]
123 type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Self::Error>> + 'c>>;
124 #[cfg(not(target_arch = "wasm32"))]
125 type Future =
126 Pin<Box<dyn Future<Output = Result<HttpResponse, Self::Error>> + Send + Sync + 'c>>;
127
128 fn call(&'c self, request: HttpRequest) -> Self::Future {
129 Box::pin(async move {
130 let response = self
131 .0
132 .execute(request.try_into().map_err(Box::new)?)
133 .await
134 .map_err(Box::new)?;
135
136 let mut builder = http::Response::builder().status(response.status());
137
138 #[cfg(not(target_arch = "wasm32"))]
139 {
140 builder = builder.version(response.version());
141 }
142
143 for (name, value) in response.headers().iter() {
144 builder = builder.header(name, value);
145 }
146
147 builder
148 .body(response.bytes().await.map_err(Box::new)?.to_vec())
149 .map_err(HttpClientError::Http)
150 })
151 }
152}
153
154#[cfg(all(feature = "blocking", not(target_arch = "wasm32")))]
155pub use blocking::ReqwestBlockingClient;
156
157#[cfg(all(feature = "blocking", not(target_arch = "wasm32")))]
158mod blocking {
159 use oauth2::{http, HttpClientError, HttpRequest, HttpResponse};
160
161 /// Synchronous `reqwest` blocking [`Client`](reqwest::blocking::Client) wrapper.
162 ///
163 /// See the [crate-level documentation](crate) for usage instructions.
164 #[derive(Clone, Debug, Default)]
165 pub struct ReqwestBlockingClient(reqwest::blocking::Client);
166
167 impl oauth2::SyncHttpClient for ReqwestBlockingClient {
168 type Error = HttpClientError<reqwest::Error>;
169
170 fn call(&self, request: HttpRequest) -> Result<HttpResponse, Self::Error> {
171 let mut response = self
172 .0
173 .execute(request.try_into().map_err(Box::new)?)
174 .map_err(Box::new)?;
175
176 let mut builder = http::Response::builder()
177 .status(response.status())
178 .version(response.version());
179
180 for (name, value) in response.headers().iter() {
181 builder = builder.header(name, value);
182 }
183
184 let mut body = Vec::new();
185 <reqwest::blocking::Response as std::io::Read>::read_to_end(&mut response, &mut body)?;
186
187 builder.body(body).map_err(HttpClientError::Http)
188 }
189 }
190
191 impl From<reqwest::blocking::Client> for ReqwestBlockingClient {
192 fn from(inner: reqwest::blocking::Client) -> Self {
193 Self(inner)
194 }
195 }
196}