1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
#![warn(missing_docs)] //! HTTP client adapter for [oauth2](https://crates.io/crates/oauth2) using the [surf](https://crates.io/crates/surf) HTTP client //! //!# Usage //! //! Just import the `http_client` function from this library and pass it into the request_async function when exchanging tokens. //! (Example taken from oauth2 docs) //!```rust //! use anyhow; //! use oauth2::{ //! AuthorizationCode, //! AuthUrl, //! ClientId, //! ClientSecret, //! CsrfToken, //! PkceCodeChallenge, //! RedirectUrl, //! Scope, //! TokenResponse, //! TokenUrl //! }; //! use oauth2::basic::BasicClient; //! use oauth2_surf::http_client; //! use url::Url; //! //! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and //! // token URL. //! let client = //! BasicClient::new( //! ClientId::new("client_id".to_string()), //! Some(ClientSecret::new("client_secret".to_string())), //! AuthUrl::new("http://authorize".to_string())?, //! Some(TokenUrl::new("http://token".to_string())?) //! ) //! // Set the URL the user will be redirected to after the authorization process. //! .set_redirect_url(RedirectUrl::new("http://redirect".to_string())?); //! //! // Generate a PKCE challenge. //! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); //! //! // Generate the full authorization URL. //! let (auth_url, csrf_token) = client //! .authorize_url(CsrfToken::new_random) //! // Set the desired scopes. //! .add_scope(Scope::new("read".to_string())) //! .add_scope(Scope::new("write".to_string())) //! // Set the PKCE code challenge. //! .set_pkce_challenge(pkce_challenge) //! .url(); //! //! // This is the URL you should redirect the user to, in order to trigger the authorization //! // process. //! println!("Browse to: {}", auth_url); //! //! // Once the user has been redirected to the redirect URL, you'll have access to the //! // authorization code. For security reasons, your code should verify that the `state` //! // parameter returned by the server matches `csrf_state`. //! //! // Now you can trade it for an access token. //! let token_result = client //! .exchange_code(AuthorizationCode::new("some authorization code".to_string())) //! // Set the PKCE code verifier. //! .set_pkce_verifier(pkce_verifier) //! .request_async(http_client) //! .await?; //! //! // Unwrapping token_result will either produce a Token or a RequestTokenError. //!``` //! use anyhow::anyhow; use http::HeaderMap; use oauth2::{HttpRequest, HttpResponse}; use thiserror::Error; /// /// Error type used by failed surf requests /// #[derive(Debug, Error)] pub enum Error { // TODO: change type when surf error implements std::error::Error /// Error returned by surf crate. #[error("surf request failed")] Surf(#[source] anyhow::Error), /// Non-surf HTTP error. #[error("HTTP error")] Http(#[source] http::Error), /// Other error. #[error("Other error: {}", _0)] Other(String), } /// /// Creates a http_client which is compatible with oauth2 crate /// pub async fn http_client(request: HttpRequest) -> Result<HttpResponse, Error> { let client = surf::client(); // Surf doesn't follow redirects by default so this is safe to SSRF // Following redirects opens the client up to SSRF vulnerabilities. let mut req_builder = surf::Request::builder(surf::http::Method::Get, request.url).body(request.body); for (name, value) in &request.headers { req_builder = req_builder.header( name.as_str(), value .to_str() .map_err(|_e| Error::Other("Unable to convert header".into()))?, ); } let request = req_builder.build(); let mut response = client .send(request) .await .map_err(|e| Error::Surf(anyhow!(e)))?; let status_code = http::StatusCode::from_u16(response.status().clone().into()).unwrap(); let chunks = response .body_bytes() .await .map_err(|e| Error::Surf(anyhow!(e)))?; Ok(HttpResponse { status_code, // Oauth2 Library only need content type headers: response .content_type() .map(|ct| { Ok(vec![( http::header::CONTENT_TYPE, http::HeaderValue::from_str(ct.essence()).map_err(|e| Error::Http(e.into()))?, )] .into_iter() .collect::<HeaderMap>()) }) .transpose()? .unwrap_or_else(HeaderMap::new), body: chunks, }) }