oauth2/lib.rs
1//! [<img alt="github" src="https://img.shields.io/badge/github-udoprog/async--oauth2-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/async-oauth2)
2//! [<img alt="crates.io" src="https://img.shields.io/crates/v/async-oauth2.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/async-oauth2)
3//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-async--oauth2-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/async-oauth2)
4//!
5//! An asynchronous OAuth2 flow implementation, trying to adhere as much as
6//! possible to [RFC 6749].
7//!
8//! <br>
9//!
10//! ## Examples
11//!
12//! To see the library in action, you can go to one of our examples:
13//!
14//! - [Google]
15//! - [Spotify]
16//! - [Twitch]
17//!
18//! If you've checked out the project they can be run like this:
19//!
20//! ```sh
21//! cargo run --manifest-path=examples/Cargo.toml --bin spotify --
22//! --client-id <client-id> --client-secret <client-secret>
23//! cargo run --manifest-path=examples/Cargo.toml --bin google --
24//! --client-id <client-id> --client-secret <client-secret>
25//! cargo run --manifest-path=examples/Cargo.toml --bin twitch --
26//! --client-id <client-id> --client-secret <client-secret>
27//! ```
28//!
29//! > Note: You need to configure your client integration to permit redirects to
30//! > `http://localhost:8080/api/auth/redirect` for these to work. How this is
31//! > done depends on the integration used.
32//!
33//! <br>
34//!
35//! ## Authorization Code Grant
36//!
37//! This is the most common OAuth2 flow.
38//!
39//! ```no_run
40//! use oauth2::*;
41//! use url::Url;
42//!
43//! pub struct ReceivedCode {
44//! pub code: AuthorizationCode,
45//! pub state: State,
46//! }
47//!
48//! # async fn listen_for_code(port: u32) -> Result<ReceivedCode, Box<dyn std::error::Error>> { todo!() }
49//! # #[tokio::main]
50//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
51//! let reqwest_client = reqwest::Client::new();
52//!
53//! // Create an OAuth2 client by specifying the client ID, client secret,
54//! // authorization URL and token URL.
55//! let mut client = Client::new(
56//! "client_id",
57//! Url::parse("http://authorize")?,
58//! Url::parse("http://token")?
59//! );
60//!
61//! client.set_client_secret("client_secret");
62//! // Set the URL the user will be redirected to after the authorization
63//! // process.
64//! client.set_redirect_url(Url::parse("http://redirect")?);
65//! // Set the desired scopes.
66//! client.add_scope("read");
67//! client.add_scope("write");
68//!
69//! // Generate the full authorization URL.
70//! let state = State::new_random();
71//! let auth_url = client.authorize_url(&state);
72//!
73//! // This is the URL you should redirect the user to, in order to trigger the
74//! // authorization process.
75//! println!("Browse to: {}", auth_url);
76//!
77//! // Once the user has been redirected to the redirect URL, you'll have the
78//! // access code. For security reasons, your code should verify that the
79//! // `state` parameter returned by the server matches `state`.
80//! let received: ReceivedCode = listen_for_code(8080).await?;
81//!
82//! if received.state != state {
83//! panic!("CSRF token mismatch :(");
84//! }
85//!
86//! // Now you can trade it for an access token.
87//! let token = client.exchange_code(received.code)
88//! .with_reqwest_client(&reqwest_client)
89//! .execute::<StandardToken>()
90//! .await?;
91//!
92//! # Ok(())
93//! # }
94//! ```
95//!
96//! <br>
97//!
98//! ## Implicit Grant
99//!
100//! This flow fetches an access token directly from the authorization endpoint.
101//!
102//! Be sure to understand the security implications of this flow before using
103//! it. In most cases the Authorization Code Grant flow above is preferred to
104//! the Implicit Grant flow.
105//!
106//! ```no_run
107//! use oauth2::*;
108//! use url::Url;
109//!
110//! pub struct ReceivedCode {
111//! pub code: AuthorizationCode,
112//! pub state: State,
113//! }
114//!
115//! # async fn get_code() -> Result<ReceivedCode, Box<dyn std::error::Error>> { todo!() }
116//! # #[tokio::main]
117//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
118//! let mut client = Client::new(
119//! "client_id",
120//! Url::parse("http://authorize")?,
121//! Url::parse("http://token")?
122//! );
123//!
124//! client.set_client_secret("client_secret");
125//!
126//! // Generate the full authorization URL.
127//! let state = State::new_random();
128//! let auth_url = client.authorize_url_implicit(&state);
129//!
130//! // This is the URL you should redirect the user to, in order to trigger the
131//! // authorization process.
132//! println!("Browse to: {}", auth_url);
133//!
134//! // Once the user has been redirected to the redirect URL, you'll have the
135//! // access code. For security reasons, your code should verify that the
136//! // `state` parameter returned by the server matches `state`.
137//! let received: ReceivedCode = get_code().await?;
138//!
139//! if received.state != state {
140//! panic!("CSRF token mismatch :(");
141//! }
142//!
143//! # Ok(()) }
144//! ```
145//!
146//! <br>
147//!
148//! ## Resource Owner Password Credentials Grant
149//!
150//! You can ask for a *password* access token by calling the
151//! `Client::exchange_password` method, while including the username and
152//! password.
153//!
154//! ```no_run
155//! use oauth2::*;
156//! use url::Url;
157//!
158//! # #[tokio::main]
159//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
160//! let reqwest_client = reqwest::Client::new();
161//!
162//! let mut client = Client::new(
163//! "client_id",
164//! Url::parse("http://authorize")?,
165//! Url::parse("http://token")?
166//! );
167//!
168//! client.set_client_secret("client_secret");
169//! client.add_scope("read");
170//!
171//! let token = client
172//! .exchange_password("user", "pass")
173//! .with_reqwest_client(&reqwest_client)
174//! .execute::<StandardToken>()
175//! .await?;
176//!
177//! # Ok(()) }
178//! ```
179//!
180//! <br>
181//!
182//! ## Client Credentials Grant
183//!
184//! You can ask for a *client credentials* access token by calling the
185//! `Client::exchange_client_credentials` method.
186//!
187//! ```no_run
188//! use oauth2::*;
189//! use url::Url;
190//!
191//! # #[tokio::main]
192//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
193//! let reqwest_client = reqwest::Client::new();
194//! let mut client = Client::new(
195//! "client_id",
196//! Url::parse("http://authorize")?,
197//! Url::parse("http://token")?
198//! );
199//!
200//! client.set_client_secret("client_secret");
201//! client.add_scope("read");
202//!
203//! let token_result = client.exchange_client_credentials()
204//! .with_reqwest_client(&reqwest_client)
205//! .execute::<StandardToken>();
206//!
207//! # Ok(()) }
208//! ```
209//!
210//! <br>
211//!
212//! ## Relationship to oauth2-rs
213//!
214//! This is a fork of [oauth2-rs].
215//!
216//! The main differences are:
217//! * Removal of unnecessary type parameters on Client ([see discussion here]).
218//! * Only support one client implementation ([reqwest]).
219//! * Remove most newtypes except `Scope` and the secret ones since they made the API harder to use.
220//!
221//! [RFC 6749]: https://tools.ietf.org/html/rfc6749
222//! [Google]: https://github.com/udoprog/async-oauth2/blob/master/examples/src/bin/google.rs
223//! [oauth2-rs]: https://github.com/ramosbugs/oauth2-rs
224//! [reqwest]: https://docs.rs/reqwest
225//! [see discussion here]: https://github.com/ramosbugs/oauth2-rs/issues/44#issuecomment-50158653
226//! [Spotify]: https://github.com/udoprog/async-oauth2/blob/master/examples/src/bin/spotify.rs
227//! [Twitch]: https://github.com/udoprog/async-oauth2/blob/master/examples/src/bin/twitch.rs
228
229#![allow(clippy::vec_init_then_push)]
230#![deny(missing_docs)]
231#![no_std]
232
233#[cfg(feature = "alloc")]
234extern crate alloc;
235
236#[cfg(not(feature = "alloc"))]
237compile_error!("The `alloc` feature is required for async-oauth2 to work. Please enable it in your Cargo.toml.");
238
239use core::error::Error;
240use core::fmt;
241use core::ops::Deref;
242use core::time::Duration;
243
244use alloc::borrow::{Cow, ToOwned};
245use alloc::string::{String, ToString};
246use alloc::vec::Vec;
247
248use base64::prelude::{Engine as _, BASE64_URL_SAFE_NO_PAD};
249use bytes::Bytes;
250use http::status::StatusCode;
251use serde::{Deserialize, Serialize};
252use serde_aux::prelude::*;
253use sha2::{Digest, Sha256};
254
255pub use url::Url;
256
257/// Indicates whether requests to the authorization server should use basic authentication or
258/// include the parameters in the request body for requests in which either is valid.
259///
260/// The default AuthType is *BasicAuth*, following the recommendation of
261/// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
262#[derive(Clone, Copy, Debug)]
263pub enum AuthType {
264 /// The client_id and client_secret will be included as part of the request body.
265 RequestBody,
266 /// The client_id and client_secret will be included using the basic auth authentication scheme.
267 BasicAuth,
268}
269
270macro_rules! redacted_debug {
271 ($name:ident) => {
272 impl fmt::Debug for $name {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 write!(f, concat!(stringify!($name), "([redacted])"))
275 }
276 }
277 };
278}
279
280/// borrowed newtype plumbing
281macro_rules! borrowed_newtype {
282 ($name:ident, $borrowed:ty) => {
283 impl Deref for $name {
284 type Target = $borrowed;
285
286 #[inline]
287 fn deref(&self) -> &Self::Target {
288 &self.0
289 }
290 }
291
292 impl<'a> From<&'a $name> for Cow<'a, $borrowed> {
293 #[inline]
294 fn from(value: &'a $name) -> Cow<'a, $borrowed> {
295 Cow::Borrowed(&value.0)
296 }
297 }
298
299 impl AsRef<$borrowed> for $name {
300 #[inline]
301 fn as_ref(&self) -> &$borrowed {
302 self
303 }
304 }
305 };
306}
307
308/// newtype plumbing
309macro_rules! newtype {
310 ($name:ident, $owned:ty, $borrowed:ty) => {
311 borrowed_newtype!($name, $borrowed);
312
313 impl<'a> From<&'a $borrowed> for $name {
314 #[inline]
315 fn from(value: &'a $borrowed) -> Self {
316 Self(value.to_owned())
317 }
318 }
319
320 impl From<$owned> for $name {
321 #[inline]
322 fn from(value: $owned) -> Self {
323 Self(value)
324 }
325 }
326
327 impl<'a> From<&'a $owned> for $name {
328 #[inline]
329 fn from(value: &'a $owned) -> Self {
330 Self(value.to_owned())
331 }
332 }
333
334 impl From<$name> for $owned {
335 #[inline]
336 fn from(value: $name) -> $owned {
337 value.0
338 }
339 }
340 };
341}
342
343/// Access token scope, as defined by the authorization server.
344#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
345pub struct Scope(String);
346newtype!(Scope, String, str);
347
348/// Code Challenge used for [PKCE]((https://tools.ietf.org/html/rfc7636)) protection via the
349/// `code_challenge` parameter.
350#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
351pub struct PkceCodeChallengeS256(String);
352newtype!(PkceCodeChallengeS256, String, str);
353
354/// Code Challenge Method used for [PKCE]((https://tools.ietf.org/html/rfc7636)) protection
355/// via the `code_challenge_method` parameter.
356#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
357pub struct PkceCodeChallengeMethod(String);
358newtype!(PkceCodeChallengeMethod, String, str);
359
360/// Client password issued to the client during the registration process described by
361/// [Section 2.2](https://tools.ietf.org/html/rfc6749#section-2.2).
362#[derive(Clone, Deserialize, Serialize)]
363pub struct ClientSecret(String);
364redacted_debug!(ClientSecret);
365newtype!(ClientSecret, String, str);
366
367/// Value used for [CSRF]((https://tools.ietf.org/html/rfc6749#section-10.12)) protection
368/// via the `state` parameter.
369#[must_use]
370#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
371pub struct State([u8; 16]);
372redacted_debug!(State);
373borrowed_newtype!(State, [u8]);
374
375impl State {
376 /// Generate a new random, base64-encoded 128-bit CSRF token.
377 #[cfg(feature = "rand")]
378 #[inline]
379 pub fn new_random() -> Self {
380 let mut random_bytes = [0u8; 16];
381 rand::fill(&mut random_bytes);
382 State(random_bytes)
383 }
384
385 /// Construct state from random bytes.
386 ///
387 /// This is potentially unsafe unless you can guarantee that the provided
388 /// bytes are generated from a cryptographically secure random number
389 /// generator.
390 ///
391 /// If you are not sure of this, enable the `rand` feature and use
392 /// [`State::new_random()`] instead.
393 #[inline]
394 pub fn from_random(bytes: [u8; 16]) -> Self {
395 State(bytes)
396 }
397
398 /// Convert into base64.
399 pub fn to_base64(&self) -> String {
400 BASE64_URL_SAFE_NO_PAD.encode(self.0)
401 }
402}
403
404impl serde::Serialize for State {
405 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
406 where
407 S: serde::Serializer,
408 {
409 self.to_base64().serialize(serializer)
410 }
411}
412
413impl<'de> serde::Deserialize<'de> for State {
414 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
415 where
416 D: serde::Deserializer<'de>,
417 {
418 let s = String::deserialize(deserializer)?;
419 let bytes = BASE64_URL_SAFE_NO_PAD
420 .decode(s)
421 .map_err(serde::de::Error::custom)?;
422 let mut buf = [0u8; 16];
423 buf.copy_from_slice(&bytes);
424 Ok(Self(buf))
425 }
426}
427
428/// Code Verifier used for [PKCE]((https://tools.ietf.org/html/rfc7636)) protection via the
429/// `code_verifier` parameter. The value must have a minimum length of 43 characters and a
430/// maximum length of 128 characters. Each character must be ASCII alphanumeric or one of
431/// the characters "-" / "." / "_" / "~".
432#[derive(Deserialize, Serialize)]
433pub struct PkceCodeVerifierS256(String);
434newtype!(PkceCodeVerifierS256, String, str);
435
436impl PkceCodeVerifierS256 {
437 /// Generate a new random, base64-encoded code verifier.
438 #[cfg(feature = "rand")]
439 pub fn new_random() -> Self {
440 PkceCodeVerifierS256::new_random_len(32)
441 }
442
443 /// Generate a new random, base64-encoded code verifier.
444 ///
445 /// # Arguments
446 ///
447 /// * `num_bytes` - Number of random bytes to generate, prior to base64-encoding.
448 /// The value must be in the range 32 to 96 inclusive in order to generate a verifier
449 /// with a suitable length.
450 #[cfg(feature = "rand")]
451 pub fn new_random_len(num_bytes: u32) -> Self {
452 // The RFC specifies that the code verifier must have "a minimum length of 43
453 // characters and a maximum length of 128 characters".
454 // This implies 32-96 octets of random data to be base64 encoded.
455 assert!((32..=96).contains(&num_bytes));
456 let random_bytes: Vec<u8> = (0..num_bytes).map(|_| rand::random::<u8>()).collect();
457 let code = BASE64_URL_SAFE_NO_PAD.encode(random_bytes);
458 assert!(code.len() >= 43 && code.len() <= 128);
459 PkceCodeVerifierS256(code)
460 }
461
462 /// Return the code challenge for the code verifier.
463 pub fn code_challenge(&self) -> PkceCodeChallengeS256 {
464 let digest = Sha256::digest(self.as_bytes());
465 PkceCodeChallengeS256::from(BASE64_URL_SAFE_NO_PAD.encode(digest))
466 }
467
468 /// Return the code challenge method for this code verifier.
469 pub fn code_challenge_method() -> PkceCodeChallengeMethod {
470 PkceCodeChallengeMethod::from("S256".to_string())
471 }
472
473 /// Return the extension params used for authorize_url.
474 pub fn authorize_url_params(&self) -> Vec<(&'static str, String)> {
475 let mut vec = Vec::with_capacity(2);
476 vec.push((
477 "code_challenge_method",
478 PkceCodeVerifierS256::code_challenge_method().into(),
479 ));
480 vec.push(("code_challenge", self.code_challenge().into()));
481 vec
482 }
483}
484
485/// Authorization code returned from the authorization endpoint.
486#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
487pub struct AuthorizationCode(String);
488redacted_debug!(AuthorizationCode);
489newtype!(AuthorizationCode, String, str);
490
491/// Refresh token used to obtain a new access token (if supported by the authorization server).
492#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
493pub struct RefreshToken(String);
494redacted_debug!(RefreshToken);
495newtype!(RefreshToken, String, str);
496
497/// Access token returned by the token endpoint and used to access protected resources.
498#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
499pub struct AccessToken(String);
500redacted_debug!(AccessToken);
501newtype!(AccessToken, String, str);
502
503/// Resource owner's password used directly as an authorization grant to obtain an access
504/// token.
505pub struct ResourceOwnerPassword(String);
506newtype!(ResourceOwnerPassword, String, str);
507
508/// Stores the configuration for an OAuth2 client.
509#[derive(Clone, Debug)]
510pub struct Client {
511 client_id: String,
512 client_secret: Option<ClientSecret>,
513 auth_url: Url,
514 auth_type: AuthType,
515 token_url: Url,
516 scopes: Vec<Scope>,
517 redirect_url: Option<Url>,
518}
519
520impl Client {
521 /// Initializes an OAuth2 client with the fields common to most OAuth2 flows.
522 ///
523 /// # Arguments
524 ///
525 /// * `client_id` - Client ID
526 /// * `auth_url` - Authorization endpoint: used by the client to obtain authorization from
527 /// the resource owner via user-agent redirection. This URL is used in all standard OAuth2
528 /// flows except the [Resource Owner Password Credentials
529 /// Grant](https://tools.ietf.org/html/rfc6749#section-4.3) and the
530 /// [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).
531 /// * `token_url` - Token endpoint: used by the client to exchange an authorization grant
532 /// (code) for an access token, typically with client authentication. This URL is used in
533 /// all standard OAuth2 flows except the
534 /// [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2). If this value is set
535 /// to `None`, the `exchange_*` methods will return `Err(ExecuteError::Other(_))`.
536 pub fn new(client_id: impl AsRef<str>, auth_url: Url, token_url: Url) -> Self {
537 Client {
538 client_id: client_id.as_ref().to_string(),
539 client_secret: None,
540 auth_url,
541 auth_type: AuthType::BasicAuth,
542 token_url,
543 scopes: Vec::new(),
544 redirect_url: None,
545 }
546 }
547
548 /// Configure the client secret to use.
549 pub fn set_client_secret(&mut self, client_secret: impl Into<ClientSecret>) {
550 self.client_secret = Some(client_secret.into());
551 }
552
553 /// Appends a new scope to the authorization URL.
554 pub fn add_scope(&mut self, scope: impl Into<Scope>) {
555 self.scopes.push(scope.into());
556 }
557
558 /// Configures the type of client authentication used for communicating with the authorization
559 /// server.
560 ///
561 /// The default is to use HTTP Basic authentication, as recommended in
562 /// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
563 pub fn set_auth_type(&mut self, auth_type: AuthType) {
564 self.auth_type = auth_type;
565 }
566
567 /// Sets the the redirect URL used by the authorization endpoint.
568 pub fn set_redirect_url(&mut self, redirect_url: Url) {
569 self.redirect_url = Some(redirect_url);
570 }
571
572 /// Produces the full authorization URL used by the
573 /// [Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1)
574 /// flow, which is the most common OAuth2 flow.
575 ///
576 /// # Arguments
577 ///
578 /// * `state` - A state value to include in the request. The authorization
579 /// server includes this value when redirecting the user-agent back to the
580 /// client.
581 ///
582 /// # Security Warning
583 ///
584 /// Callers should use a fresh, unpredictable `state` for each authorization
585 /// request and verify that this value matches the `state` parameter passed
586 /// by the authorization server to the redirect URI. Doing so mitigates
587 /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12)
588 /// attacks.
589 pub fn authorize_url(&self, state: &State) -> Url {
590 self.authorize_url_impl("code", state)
591 }
592
593 /// Produces the full authorization URL used by the
594 /// [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2) flow.
595 ///
596 /// # Arguments
597 ///
598 /// * `state` - A state value to include in the request. The authorization
599 /// server includes this value when redirecting the user-agent back to the
600 /// client.
601 ///
602 /// # Security Warning
603 ///
604 /// Callers should use a fresh, unpredictable `state` for each authorization request and verify
605 /// that this value matches the `state` parameter passed by the authorization server to the
606 /// redirect URI. Doing so mitigates
607 /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12)
608 /// attacks.
609 pub fn authorize_url_implicit(&self, state: &State) -> Url {
610 self.authorize_url_impl("token", state)
611 }
612
613 fn authorize_url_impl(&self, response_type: &str, state: &State) -> Url {
614 let scopes = self
615 .scopes
616 .iter()
617 .map(|s| s.to_string())
618 .collect::<Vec<_>>()
619 .join(" ");
620
621 let mut url = self.auth_url.clone();
622
623 {
624 let mut query = url.query_pairs_mut();
625
626 query.append_pair("response_type", response_type);
627 query.append_pair("client_id", &self.client_id);
628
629 if let Some(ref redirect_url) = self.redirect_url {
630 query.append_pair("redirect_uri", redirect_url.as_str());
631 }
632
633 if !scopes.is_empty() {
634 query.append_pair("scope", &scopes);
635 }
636
637 query.append_pair("state", &state.to_base64());
638 }
639
640 url
641 }
642
643 /// Exchanges a code produced by a successful authorization process with an access token.
644 ///
645 /// Acquires ownership of the `code` because authorization codes may only be used to retrieve
646 /// an access token from the authorization server.
647 ///
648 /// See https://tools.ietf.org/html/rfc6749#section-4.1.3
649 pub fn exchange_code(&self, code: impl Into<AuthorizationCode>) -> Request<'_> {
650 let code = code.into();
651
652 self.request_token()
653 .param("grant_type", "authorization_code")
654 .param("code", code.to_string())
655 }
656
657 /// Requests an access token for the *password* grant type.
658 ///
659 /// See https://tools.ietf.org/html/rfc6749#section-4.3.2
660 pub fn exchange_password(
661 &self,
662 username: impl AsRef<str>,
663 password: impl AsRef<str>,
664 ) -> Request<'_> {
665 let username = username.as_ref();
666 let password = password.as_ref();
667
668 let mut builder = self
669 .request_token()
670 .param("grant_type", "password")
671 .param("username", username.to_string())
672 .param("password", password.to_string());
673
674 // Generate the space-delimited scopes String before initializing params so that it has
675 // a long enough lifetime.
676 if !self.scopes.is_empty() {
677 let scopes = self
678 .scopes
679 .iter()
680 .map(|s| s.to_string())
681 .collect::<Vec<_>>()
682 .join(" ");
683
684 builder = builder.param("scope", scopes);
685 }
686
687 builder
688 }
689
690 /// Requests an access token for the *client credentials* grant type.
691 ///
692 /// See https://tools.ietf.org/html/rfc6749#section-4.4.2
693 pub fn exchange_client_credentials(&self) -> Request<'_> {
694 let mut builder = self
695 .request_token()
696 .param("grant_type", "client_credentials");
697
698 // Generate the space-delimited scopes String before initializing params so that it has
699 // a long enough lifetime.
700 if !self.scopes.is_empty() {
701 let scopes = self
702 .scopes
703 .iter()
704 .map(|s| s.to_string())
705 .collect::<Vec<_>>()
706 .join(" ");
707
708 builder = builder.param("scope", scopes);
709 }
710
711 builder
712 }
713
714 /// Exchanges a refresh token for an access token
715 ///
716 /// See https://tools.ietf.org/html/rfc6749#section-6
717 pub fn exchange_refresh_token(&self, refresh_token: &RefreshToken) -> Request<'_> {
718 self.request_token()
719 .param("grant_type", "refresh_token")
720 .param("refresh_token", refresh_token.to_string())
721 }
722
723 /// Construct a request builder for the token URL.
724 fn request_token(&self) -> Request<'_> {
725 Request {
726 token_url: &self.token_url,
727 auth_type: self.auth_type,
728 client_id: &self.client_id,
729 client_secret: self.client_secret.as_ref(),
730 redirect_url: self.redirect_url.as_ref(),
731 params: Vec::new(),
732 }
733 }
734}
735
736/// A request wrapped in a client, ready to be executed.
737#[cfg(feature = "reqwest")]
738pub struct ReqwestClientRequest<'a> {
739 request: Request<'a>,
740 client: &'a reqwest::Client,
741}
742
743#[cfg(feature = "reqwest")]
744impl ReqwestClientRequest<'_> {
745 /// Execute the token request.
746 pub async fn execute<T>(self) -> Result<T, ExecuteError>
747 where
748 T: for<'de> Deserialize<'de>,
749 {
750 use reqwest::{header, Method};
751
752 const CONTENT_TYPE_JSON: &str = "application/json";
753
754 fn url_encode(s: &str) -> String {
755 url::form_urlencoded::byte_serialize(s.as_bytes()).collect::<String>()
756 }
757
758 let mut request = self
759 .client
760 .request(Method::POST, self.request.token_url.clone());
761
762 // Section 5.1 of RFC 6749 (https://tools.ietf.org/html/rfc6749#section-5.1) only permits
763 // JSON responses for this request. Some providers such as GitHub have off-spec behavior
764 // and not only support different response formats, but have non-JSON defaults. Explicitly
765 // request JSON here.
766 request = request.header(
767 header::ACCEPT,
768 header::HeaderValue::from_static(CONTENT_TYPE_JSON),
769 );
770
771 let request = {
772 let mut form = url::form_urlencoded::Serializer::new(String::new());
773
774 // FIXME: add support for auth extensions? e.g., client_secret_jwt and private_key_jwt
775 match self.request.auth_type {
776 AuthType::RequestBody => {
777 form.append_pair("client_id", self.request.client_id);
778
779 if let Some(client_secret) = self.request.client_secret {
780 form.append_pair("client_secret", client_secret);
781 }
782 }
783 AuthType::BasicAuth => {
784 // Section 2.3.1 of RFC 6749 requires separately url-encoding the id and secret
785 // before using them as HTTP Basic auth username and password. Note that this is
786 // not standard for ordinary Basic auth, so curl won't do it for us.
787 let username = url_encode(self.request.client_id);
788
789 let password = self
790 .request
791 .client_secret
792 .map(|client_secret| url_encode(client_secret));
793
794 request = request.basic_auth(username, password.as_ref());
795 }
796 }
797
798 for (key, value) in self.request.params {
799 form.append_pair(key.as_ref(), value.as_ref());
800 }
801
802 if let Some(redirect_url) = &self.request.redirect_url {
803 form.append_pair("redirect_uri", redirect_url.as_str());
804 }
805
806 request = request.header(
807 header::CONTENT_TYPE,
808 header::HeaderValue::from_static("application/x-www-form-urlencoded"),
809 );
810
811 request.body(form.finish().into_bytes())
812 };
813
814 let res = request
815 .send()
816 .await
817 .map_err(|error| ExecuteErrorKind::SendError { error })?;
818
819 let status = res.status();
820
821 let body = res
822 .bytes()
823 .await
824 .map_err(|error| ExecuteErrorKind::BytesError { error })?;
825
826 if body.is_empty() {
827 return Err(ExecuteError::from(ExecuteErrorKind::EmptyResponse {
828 status,
829 }));
830 }
831
832 if !status.is_success() {
833 let error = match serde_json::from_slice::<ErrorResponse>(body.as_ref()) {
834 Ok(error) => error,
835 Err(error) => {
836 return Err(ExecuteError::from(ExecuteErrorKind::BadResponse {
837 status,
838 error,
839 body,
840 }));
841 }
842 };
843
844 return Err(ExecuteError::from(ExecuteErrorKind::ErrorResponse {
845 status,
846 error,
847 }));
848 }
849
850 let value = serde_json::from_slice(body.as_ref()).map_err(|error| {
851 ExecuteErrorKind::BadResponse {
852 status,
853 error,
854 body,
855 }
856 })?;
857
858 Ok(value)
859 }
860}
861
862/// A token request that is in progress.
863#[cfg_attr(not(any(feature = "reqwest")), allow(unused))]
864pub struct Request<'a> {
865 token_url: &'a Url,
866 auth_type: AuthType,
867 client_id: &'a str,
868 client_secret: Option<&'a ClientSecret>,
869 /// Configured redirect URL.
870 redirect_url: Option<&'a Url>,
871 /// Extra parameters.
872 params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
873}
874
875impl<'a> Request<'a> {
876 /// Set an additional request param.
877 pub fn param(mut self, key: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Self {
878 self.params.push((key.into(), value.into()));
879 self
880 }
881
882 /// Wrap the request in a client.
883 #[cfg(feature = "reqwest")]
884 pub fn with_reqwest_client(self, client: &'a reqwest::Client) -> ReqwestClientRequest<'a> {
885 ReqwestClientRequest {
886 request: self,
887 client,
888 }
889 }
890}
891
892/// Basic OAuth2 authorization token types.
893#[derive(Clone, Debug, PartialEq, Serialize)]
894#[serde(rename_all = "lowercase")]
895pub enum TokenType {
896 /// Bearer token
897 /// ([OAuth 2.0 Bearer Tokens - RFC 6750](https://tools.ietf.org/html/rfc6750)).
898 Bearer,
899 /// MAC ([OAuth 2.0 Message Authentication Code (MAC)
900 /// Tokens](https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05)).
901 Mac,
902}
903
904impl<'de> serde::de::Deserialize<'de> for TokenType {
905 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
906 where
907 D: serde::de::Deserializer<'de>,
908 {
909 let value = String::deserialize(deserializer)?.to_lowercase();
910
911 return match value.as_str() {
912 "bearer" => Ok(TokenType::Bearer),
913 "mac" => Ok(TokenType::Mac),
914 other => Err(serde::de::Error::custom(UnknownVariantError(
915 other.to_string(),
916 ))),
917 };
918
919 #[derive(Debug)]
920 struct UnknownVariantError(String);
921
922 impl Error for UnknownVariantError {}
923
924 impl fmt::Display for UnknownVariantError {
925 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
926 write!(fmt, "unsupported variant: {}", self.0)
927 }
928 }
929 }
930}
931
932/// Common methods shared by all OAuth2 token implementations.
933///
934/// The methods in this trait are defined in
935/// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1). This trait exists
936/// separately from the `StandardToken` struct to support customization by clients,
937/// such as supporting interoperability with non-standards-complaint OAuth2 providers.
938pub trait Token
939where
940 Self: for<'a> serde::de::Deserialize<'a>,
941{
942 /// REQUIRED. The access token issued by the authorization server.
943 fn access_token(&self) -> &AccessToken;
944
945 /// REQUIRED. The type of the token issued as described in
946 /// [Section 7.1](https://tools.ietf.org/html/rfc6749#section-7.1).
947 /// Value is case insensitive and deserialized to the generic `TokenType` parameter.
948 fn token_type(&self) -> &TokenType;
949
950 /// RECOMMENDED. The lifetime in seconds of the access token. For example, the value 3600
951 /// denotes that the access token will expire in one hour from the time the response was
952 /// generated. If omitted, the authorization server SHOULD provide the expiration time via
953 /// other means or document the default value.
954 fn expires_in(&self) -> Option<Duration>;
955
956 /// OPTIONAL. The refresh token, which can be used to obtain new access tokens using the same
957 /// authorization grant as described in
958 /// [Section 6](https://tools.ietf.org/html/rfc6749#section-6).
959 fn refresh_token(&self) -> Option<&RefreshToken>;
960
961 /// OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The
962 /// scipe of the access token as described by
963 /// [Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3). If included in the response,
964 /// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
965 /// the response, this field is `None`.
966 fn scopes(&self) -> Option<&Vec<Scope>>;
967}
968
969/// Standard OAuth2 token response.
970///
971/// This struct includes the fields defined in
972/// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1), as well as
973/// extensions defined by the `EF` type parameter.
974#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
975pub struct StandardToken {
976 access_token: AccessToken,
977 token_type: TokenType,
978 #[serde(
979 skip_serializing_if = "Option::is_none",
980 deserialize_with = "deserialize_option_number_from_string"
981 )]
982 expires_in: Option<u64>,
983 #[serde(skip_serializing_if = "Option::is_none")]
984 refresh_token: Option<RefreshToken>,
985 #[serde(rename = "scope")]
986 #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
987 #[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
988 #[serde(skip_serializing_if = "Option::is_none")]
989 #[serde(default)]
990 scopes: Option<Vec<Scope>>,
991}
992
993impl Token for StandardToken {
994 /// REQUIRED. The access token issued by the authorization server.
995 fn access_token(&self) -> &AccessToken {
996 &self.access_token
997 }
998
999 /// REQUIRED. The type of the token issued as described in
1000 /// [Section 7.1](https://tools.ietf.org/html/rfc6749#section-7.1).
1001 /// Value is case insensitive and deserialized to the generic `TokenType` parameter.
1002 fn token_type(&self) -> &TokenType {
1003 &self.token_type
1004 }
1005
1006 /// RECOMMENDED. The lifetime in seconds of the access token. For example, the value 3600
1007 /// denotes that the access token will expire in one hour from the time the response was
1008 /// generated. If omitted, the authorization server SHOULD provide the expiration time via
1009 /// other means or document the default value.
1010 fn expires_in(&self) -> Option<Duration> {
1011 self.expires_in.map(Duration::from_secs)
1012 }
1013
1014 /// OPTIONAL. The refresh token, which can be used to obtain new access tokens using the same
1015 /// authorization grant as described in
1016 /// [Section 6](https://tools.ietf.org/html/rfc6749#section-6).
1017 fn refresh_token(&self) -> Option<&RefreshToken> {
1018 self.refresh_token.as_ref()
1019 }
1020
1021 /// OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The
1022 /// scipe of the access token as described by
1023 /// [Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3). If included in the response,
1024 /// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
1025 /// the response, this field is `None`.
1026 fn scopes(&self) -> Option<&Vec<Scope>> {
1027 self.scopes.as_ref()
1028 }
1029}
1030
1031/// These error types are defined in
1032/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2).
1033#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
1034#[serde(rename_all = "snake_case")]
1035pub enum ErrorField {
1036 /// The request is missing a required parameter, includes an unsupported parameter value
1037 /// (other than grant type), repeats a parameter, includes multiple credentials, utilizes
1038 /// more than one mechanism for authenticating the client, or is otherwise malformed.
1039 InvalidRequest,
1040 /// Client authentication failed (e.g., unknown client, no client authentication included,
1041 /// or unsupported authentication method).
1042 InvalidClient,
1043 /// The provided authorization grant (e.g., authorization code, resource owner credentials)
1044 /// or refresh token is invalid, expired, revoked, does not match the redirection URI used
1045 /// in the authorization request, or was issued to another client.
1046 InvalidGrant,
1047 /// The authenticated client is not authorized to use this authorization grant type.
1048 UnauthorizedClient,
1049 /// The authorization grant type is not supported by the authorization server.
1050 UnsupportedGrantType,
1051 /// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the
1052 /// resource owner.
1053 InvalidScope,
1054 /// Other error type.
1055 Other(String),
1056}
1057
1058impl fmt::Display for ErrorField {
1059 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
1060 use self::ErrorField::*;
1061
1062 match *self {
1063 InvalidRequest => "invalid_request".fmt(fmt),
1064 InvalidClient => "invalid_client".fmt(fmt),
1065 InvalidGrant => "invalid_grant".fmt(fmt),
1066 UnauthorizedClient => "unauthorized_client".fmt(fmt),
1067 UnsupportedGrantType => "unsupported_grant_type".fmt(fmt),
1068 InvalidScope => "invalid_scope".fmt(fmt),
1069 Other(ref value) => value.fmt(fmt),
1070 }
1071 }
1072}
1073
1074/// Error response returned by server after requesting an access token.
1075///
1076/// The fields in this structure are defined in
1077/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2).
1078#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1079pub struct ErrorResponse {
1080 /// A single ASCII error code.
1081 pub error: ErrorField,
1082 #[serde(default)]
1083 #[serde(skip_serializing_if = "Option::is_none")]
1084 /// Human-readable ASCII text providing additional information, used to assist
1085 /// the client developer in understanding the error that occurred.
1086 pub error_description: Option<String>,
1087 #[serde(default)]
1088 #[serde(skip_serializing_if = "Option::is_none")]
1089 /// A URI identifying a human-readable web page with information about the error,
1090 /// used to provide the client developer with additional information about the error.
1091 pub error_uri: Option<String>,
1092}
1093
1094impl fmt::Display for ErrorResponse {
1095 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1096 let mut formatted = self.error.to_string();
1097
1098 if let Some(error_description) = self.error_description.as_ref() {
1099 formatted.push_str(": ");
1100 formatted.push_str(error_description);
1101 }
1102
1103 if let Some(error_uri) = self.error_uri.as_ref() {
1104 formatted.push_str(" / See ");
1105 formatted.push_str(error_uri);
1106 }
1107
1108 write!(f, "{formatted}")
1109 }
1110}
1111
1112impl Error for ErrorResponse {}
1113
1114/// Errors when creating new clients.
1115pub struct NewClientError {
1116 kind: NewClientErrorKind,
1117}
1118
1119impl Error for NewClientError {
1120 #[inline]
1121 fn source(&self) -> Option<&(dyn Error + 'static)> {
1122 match self.kind {
1123 #[cfg(feature = "reqwest")]
1124 NewClientErrorKind::Reqwest { ref error } => Some(error),
1125 }
1126 }
1127}
1128
1129impl fmt::Display for NewClientError {
1130 #[inline]
1131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1132 self.kind.fmt(f)
1133 }
1134}
1135
1136impl fmt::Debug for NewClientError {
1137 #[inline]
1138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1139 self.kind.fmt(f)
1140 }
1141}
1142
1143#[derive(Debug)]
1144enum NewClientErrorKind {
1145 /// Error creating underlying reqwest client.
1146 #[cfg(feature = "reqwest")]
1147 Reqwest { error: reqwest::Error },
1148}
1149
1150impl fmt::Display for NewClientErrorKind {
1151 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
1152 match *self {
1153 #[cfg(feature = "reqwest")]
1154 NewClientErrorKind::Reqwest { .. } => "error constructing reqwest client".fmt(_f),
1155 }
1156 }
1157}
1158
1159#[cfg(feature = "reqwest")]
1160impl From<reqwest::Error> for NewClientError {
1161 #[inline]
1162 fn from(error: reqwest::Error) -> Self {
1163 Self {
1164 kind: NewClientErrorKind::Reqwest { error },
1165 }
1166 }
1167}
1168
1169/// Error encountered while requesting access token.
1170pub struct ExecuteError {
1171 kind: ExecuteErrorKind,
1172}
1173
1174impl From<ExecuteErrorKind> for ExecuteError {
1175 #[inline]
1176 fn from(kind: ExecuteErrorKind) -> Self {
1177 Self { kind }
1178 }
1179}
1180
1181impl Error for ExecuteError {
1182 #[inline]
1183 fn source(&self) -> Option<&(dyn Error + 'static)> {
1184 match self.kind {
1185 #[cfg(feature = "reqwest")]
1186 ExecuteErrorKind::SendError { ref error } => Some(error),
1187 #[cfg(feature = "reqwest")]
1188 ExecuteErrorKind::BytesError { ref error } => Some(error),
1189 ExecuteErrorKind::BadResponse { ref error, .. } => Some(error),
1190 ExecuteErrorKind::ErrorResponse { ref error, .. } => Some(error),
1191 ExecuteErrorKind::EmptyResponse { .. } => None,
1192 }
1193 }
1194}
1195
1196impl fmt::Display for ExecuteError {
1197 #[inline]
1198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1199 self.kind.fmt(f)
1200 }
1201}
1202
1203impl fmt::Debug for ExecuteError {
1204 #[inline]
1205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1206 self.kind.fmt(f)
1207 }
1208}
1209
1210#[derive(Debug)]
1211enum ExecuteErrorKind {
1212 #[cfg(feature = "reqwest")]
1213 SendError {
1214 /// Original request error.
1215 error: reqwest::Error,
1216 },
1217 #[cfg(feature = "reqwest")]
1218 BytesError {
1219 /// Original request error.
1220 error: reqwest::Error,
1221 },
1222 /// Failed to parse server response. Parse errors may occur while parsing either successful
1223 /// or error responses.
1224 #[cfg_attr(not(any(feature = "reqwest")), allow(unused))]
1225 BadResponse {
1226 /// The status code associated with the response.
1227 status: StatusCode,
1228 /// The body that couldn't be deserialized.
1229 body: Bytes,
1230 /// Deserialization error.
1231 error: serde_json::error::Error,
1232 },
1233 /// Response with non-successful status code and a body that could be
1234 /// successfully deserialized as an [ErrorResponse].
1235 #[cfg_attr(not(any(feature = "reqwest")), allow(unused))]
1236 ErrorResponse {
1237 /// The status code associated with the response.
1238 status: StatusCode,
1239 /// The deserialized response.
1240 error: ErrorResponse,
1241 },
1242 /// Server response was empty.
1243 #[cfg_attr(not(any(feature = "reqwest")), allow(unused))]
1244 EmptyResponse {
1245 /// The status code associated with the empty response.
1246 status: StatusCode,
1247 },
1248}
1249
1250impl fmt::Display for ExecuteErrorKind {
1251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1252 match *self {
1253 #[cfg(feature = "reqwest")]
1254 ExecuteErrorKind::SendError { .. } => "error sending request".fmt(f),
1255 #[cfg(feature = "reqwest")]
1256 ExecuteErrorKind::BytesError { .. } => "error reading response bytes".fmt(f),
1257 ExecuteErrorKind::BadResponse { status, .. } => {
1258 write!(f, "malformed server response: {status}")
1259 }
1260 ExecuteErrorKind::ErrorResponse { status, .. } => {
1261 write!(f, "request resulted in error response: {status}")
1262 }
1263 ExecuteErrorKind::EmptyResponse { status } => {
1264 write!(f, "request resulted in empty response: {status}")
1265 }
1266 }
1267 }
1268}
1269
1270impl ExecuteError {
1271 /// Access the status code of the error if available.
1272 #[inline]
1273 pub fn status(&self) -> Option<StatusCode> {
1274 match self.kind {
1275 #[cfg(feature = "reqwest")]
1276 ExecuteErrorKind::SendError { ref error } => error.status(),
1277 #[cfg(feature = "reqwest")]
1278 ExecuteErrorKind::BytesError { ref error } => error.status(),
1279 ExecuteErrorKind::BadResponse { status, .. } => Some(status),
1280 ExecuteErrorKind::ErrorResponse { status, .. } => Some(status),
1281 ExecuteErrorKind::EmptyResponse { status, .. } => Some(status),
1282 }
1283 }
1284
1285 /// The original response body if available.
1286 pub fn body(&self) -> Option<&Bytes> {
1287 match self.kind {
1288 ExecuteErrorKind::BadResponse { ref body, .. } => Some(body),
1289 _ => None,
1290 }
1291 }
1292}
1293
1294/// Helper methods used by OAuth2 implementations/extensions.
1295pub mod helpers {
1296 use alloc::string::{String, ToString};
1297 use alloc::vec::Vec;
1298
1299 use serde::{Deserialize, Deserializer, Serializer};
1300 use url::Url;
1301
1302 /// Serde space-delimited string deserializer for a `Vec<String>`.
1303 ///
1304 /// This function splits a JSON string at each space character into a `Vec<String>` .
1305 ///
1306 /// # Example
1307 ///
1308 /// In example below, the JSON value `{"items": "foo bar baz"}` would deserialize to:
1309 ///
1310 /// ```
1311 /// # struct GroceryBasket {
1312 /// # items: Vec<String>,
1313 /// # }
1314 /// # fn main() {
1315 /// GroceryBasket {
1316 /// items: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
1317 /// };
1318 /// # }
1319 /// ```
1320 ///
1321 /// Note: this example does not compile automatically due to
1322 /// [Rust issue #29286](https://github.com/rust-lang/rust/issues/29286).
1323 ///
1324 /// ```
1325 /// # /*
1326 /// use serde::Deserialize;
1327 ///
1328 /// #[derive(Deserialize)]
1329 /// struct GroceryBasket {
1330 /// #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
1331 /// items: Vec<String>,
1332 /// }
1333 /// # */
1334 /// ```
1335 pub fn deserialize_space_delimited_vec<'de, T, D>(deserializer: D) -> Result<T, D::Error>
1336 where
1337 T: Default + Deserialize<'de>,
1338 D: Deserializer<'de>,
1339 {
1340 use serde::de::Error;
1341 use serde_json::Value;
1342
1343 if let Some(space_delimited) = Option::<String>::deserialize(deserializer)? {
1344 let entries = space_delimited
1345 .split(' ')
1346 .map(|s| Value::String(s.to_string()))
1347 .collect();
1348 return T::deserialize(Value::Array(entries)).map_err(Error::custom);
1349 }
1350
1351 // If the JSON value is null, use the default value.
1352 Ok(T::default())
1353 }
1354
1355 /// Serde space-delimited string serializer for an `Option<Vec<String>>`.
1356 ///
1357 /// This function serializes a string vector into a single space-delimited string.
1358 /// If `string_vec_opt` is `None`, the function serializes it as `None` (e.g., `null`
1359 /// in the case of JSON serialization).
1360 pub fn serialize_space_delimited_vec<T, S>(
1361 vec_opt: &Option<Vec<T>>,
1362 serializer: S,
1363 ) -> Result<S::Ok, S::Error>
1364 where
1365 T: AsRef<str>,
1366 S: Serializer,
1367 {
1368 if let Some(ref vec) = *vec_opt {
1369 let space_delimited = vec.iter().map(|s| s.as_ref()).collect::<Vec<_>>().join(" ");
1370 serializer.serialize_str(&space_delimited)
1371 } else {
1372 serializer.serialize_none()
1373 }
1374 }
1375
1376 /// Serde string deserializer for a `Url`.
1377 pub fn deserialize_url<'de, D>(deserializer: D) -> Result<Url, D::Error>
1378 where
1379 D: Deserializer<'de>,
1380 {
1381 use serde::de::Error;
1382 let url_str = String::deserialize(deserializer)?;
1383 Url::parse(url_str.as_ref()).map_err(Error::custom)
1384 }
1385
1386 /// Serde string serializer for a `Url`.
1387 pub fn serialize_url<S>(url: &Url, serializer: S) -> Result<S::Ok, S::Error>
1388 where
1389 S: Serializer,
1390 {
1391 serializer.serialize_str(url.as_str())
1392 }
1393}