Add support for reqwest-middleware

This commit is contained in:
Aode (lion) 2021-12-05 16:45:37 -06:00
parent 53be90297d
commit 361d15a126
4 changed files with 186 additions and 36 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-reqwest" name = "http-signature-normalization-reqwest"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.2.1" version = "0.3.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -12,6 +12,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["sha-2", "sha-3"] default = ["sha-2", "sha-3"]
middleware = ["reqwest-middleware"]
digest = ["base64", "tokio"] digest = ["base64", "tokio"]
sha-2 = ["digest", "sha2"] sha-2 = ["digest", "sha2"]
sha-3 = ["digest", "sha3"] sha-3 = ["digest", "sha3"]
@ -28,12 +29,13 @@ chrono = "0.4.10"
http = "0.2.0" http = "0.2.0"
http-signature-normalization = { version = "0.5.1", path = ".." } http-signature-normalization = { version = "0.5.1", path = ".." }
reqwest = { version = "0.11", default-features = false, features = ["json"] } reqwest = { version = "0.11", default-features = false, features = ["json"] }
reqwest-middleware = { version = "0.1.2", optional = true }
sha2 = { version = "0.9", optional = true } sha2 = { version = "0.9", optional = true }
sha3 = { version = "0.9", optional = true } sha3 = { version = "0.9", optional = true }
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1", default-features = false, features = ["rt"], optional = true } tokio = { version = "1", default-features = false, features = ["rt"], optional = true }
[dev-dependencies] [dev-dependencies]
httpdate = "1.0.2"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] } tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] }
time = "0.2"

View file

@ -1,26 +1,27 @@
use std::time::SystemTime;
use http_signature_normalization_reqwest::prelude::*; use http_signature_normalization_reqwest::prelude::*;
use httpdate::HttpDate;
use reqwest::{header::DATE, Client}; use reqwest::{header::DATE, Client};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use time::OffsetDateTime;
async fn request(config: Config) -> Result<(), Box<dyn std::error::Error>> { async fn request(config: Config) -> Result<(), Box<dyn std::error::Error>> {
let digest = Sha256::new(); let digest = Sha256::new();
let response = Client::new() let client = Client::new();
let request = client
.post("http://127.0.0.1:8010/") .post("http://127.0.0.1:8010/")
.header("User-Agent", "Reqwest") .header("User-Agent", "Reqwest")
.header("Accept", "text/plain") .header("Accept", "text/plain")
.header( .header(DATE, HttpDate::from(SystemTime::now()).to_string())
DATE,
OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT"),
)
.signature_with_digest(config, "my-key-id", digest, "Hewwo-owo", |s| { .signature_with_digest(config, "my-key-id", digest, "Hewwo-owo", |s| {
println!("Signing String\n{}", s); println!("Signing String\n{}", s);
Ok(base64::encode(s)) as Result<_, MyError> Ok(base64::encode(s)) as Result<_, MyError>
}) })
.await?; .await?;
let body = response.bytes().await.map_err(MyError::Body)?; let body = client.execute(request).await?.bytes().await?;
println!("{:?}", body); println!("{:?}", body);
Ok(()) Ok(())

View file

@ -1,5 +1,5 @@
use crate::{Config, Sign, SignError}; use crate::{Config, Sign, SignError};
use reqwest::{Body, RequestBuilder}; use reqwest::{Body, Request, RequestBuilder};
use std::{fmt::Display, future::Future, pin::Pin}; use std::{fmt::Display, future::Future, pin::Pin};
#[cfg(feature = "sha-2")] #[cfg(feature = "sha-2")]
@ -29,7 +29,7 @@ pub trait SignExt: Sign {
digest: D, digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<reqwest::Response, E>>>> ) -> Pin<Box<dyn Future<Output = Result<Request, E>>>>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error>,
@ -45,7 +45,7 @@ pub trait SignExt: Sign {
digest: D, digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<reqwest::Response, E>>>> ) -> Pin<Box<dyn Future<Output = Result<Request, E>>>>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error>,
@ -63,7 +63,7 @@ impl SignExt for RequestBuilder {
mut digest: D, mut digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<reqwest::Response, E>>>> ) -> Pin<Box<dyn Future<Output = Result<Request, E>>>>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error>,
@ -80,11 +80,13 @@ impl SignExt for RequestBuilder {
.await .await
.map_err(|_| SignError::Canceled)?; .map_err(|_| SignError::Canceled)?;
let c = self let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest)) .header("Digest", format!("{}={}", D::NAME, digest))
.authorization_signature(&config, key_id, f)?; .authorization_signature(&config, key_id, f)?;
c.body(v).send().await.map_err(E::from) *req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
}) })
} }
@ -95,7 +97,7 @@ impl SignExt for RequestBuilder {
mut digest: D, mut digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<reqwest::Response, E>>>> ) -> Pin<Box<dyn Future<Output = Result<Request, E>>>>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error>,
@ -112,11 +114,91 @@ impl SignExt for RequestBuilder {
.await .await
.map_err(|_| SignError::Canceled)?; .map_err(|_| SignError::Canceled)?;
let c = self let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest)) .header("Digest", format!("{}={}", D::NAME, digest))
.signature(&config, key_id, f)?; .signature(&config, key_id, f)?;
c.body(v).send().await.map_err(E::from) *req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
}) })
} }
} }
#[cfg(feature = "middleware")]
mod middleware {
use super::{Config, DigestCreate, Sign, SignError, SignExt};
use reqwest::{Body, Request};
use reqwest_middleware::RequestBuilder;
use std::{fmt::Display, future::Future, pin::Pin};
impl SignExt for RequestBuilder {
fn authorization_signature_with_digest<F, E, K, D, V>(
self,
config: Config,
key_id: K,
mut digest: D,
v: V,
f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>>>>
where
F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>,
K: Display + 'static,
D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized,
{
Box::pin(async move {
let (v, digest) = tokio::task::spawn_blocking(move || {
let digest = digest.compute(v.as_ref());
(v, digest)
})
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.authorization_signature(&config, key_id, f)?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
})
}
fn signature_with_digest<F, E, K, D, V>(
self,
config: Config,
key_id: K,
mut digest: D,
v: V,
f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>>>>
where
F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>,
K: Display + 'static,
D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized,
{
Box::pin(async move {
let (v, digest) = tokio::task::spawn_blocking(move || {
let digest = digest.compute(v.as_ref());
(v, digest)
})
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.signature(&config, key_id, f)?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
})
}
}
}

View file

@ -34,7 +34,12 @@ pub struct Config {
/// A trait implemented by the reqwest RequestBuilder type to add an HTTP Signature to the request /// A trait implemented by the reqwest RequestBuilder type to add an HTTP Signature to the request
pub trait Sign { pub trait Sign {
/// Add an Authorization Signature to the request /// Add an Authorization Signature to the request
fn authorization_signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E> fn authorization_signature<F, E, K>(
self,
config: &Config,
key_id: K,
f: F,
) -> Result<Request, E>
where where
Self: Sized, Self: Sized,
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E>,
@ -42,7 +47,7 @@ pub trait Sign {
K: Display; K: Display;
/// Add a Signature to the request /// Add a Signature to the request
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E> fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E>
where where
Self: Sized, Self: Sized,
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E>,
@ -139,38 +144,46 @@ impl Config {
} }
impl Sign for RequestBuilder { impl Sign for RequestBuilder {
fn authorization_signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E> fn authorization_signature<F, E, K>(
self,
config: &Config,
key_id: K,
f: F,
) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E>,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error>,
K: Display, K: Display,
{ {
if let Some(builder) = self.try_clone() { let mut request = self.build()?;
let request = builder.build()?; let signed = prepare(&request, config, key_id, f)?;
let signed = prepare(&request, config, key_id, f)?;
let auth_header = signed.authorization_header(); let auth_header = signed.authorization_header();
return Ok(self.header("Authorization", auth_header)); request.headers_mut().insert(
} "Authorization",
auth_header.parse().map_err(SignError::NewHeader)?,
);
Err(SignError::BodyPresent.into()) Ok(request)
} }
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E> fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E>,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error>,
K: Display, K: Display,
{ {
if let Some(builder) = self.try_clone() { let mut request = self.build()?;
let request = builder.build()?; let signed = prepare(&request, config, key_id, f)?;
let signed = prepare(&request, config, key_id, f)?;
let sig_header = signed.signature_header(); let sig_header = signed.signature_header();
return Ok(self.header("Signature", sig_header));
}
Err(SignError::BodyPresent.into()) request.headers_mut().insert(
"Signature",
sig_header.parse().map_err(SignError::NewHeader)?,
);
Ok(request)
} }
} }
@ -214,3 +227,55 @@ where
let signed = unsigned.sign(key_id.to_string(), f)?; let signed = unsigned.sign(key_id.to_string(), f)?;
Ok(signed) Ok(signed)
} }
#[cfg(feature = "middleware")]
mod middleware {
use super::{prepare, Config, Sign, SignError};
use reqwest::Request;
use reqwest_middleware::RequestBuilder;
use std::fmt::Display;
impl Sign for RequestBuilder {
fn authorization_signature<F, E, K>(
self,
config: &Config,
key_id: K,
f: F,
) -> Result<Request, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<SignError> + From<reqwest::Error>,
K: Display,
{
let mut request = self.build()?;
let signed = prepare(&request, config, key_id, f)?;
let auth_header = signed.authorization_header();
request.headers_mut().insert(
"Authorization",
auth_header.parse().map_err(SignError::NewHeader)?,
);
Ok(request)
}
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<SignError> + From<reqwest::Error>,
K: Display,
{
let mut request = self.build()?;
let signed = prepare(&request, config, key_id, f)?;
let sig_header = signed.signature_header();
request.headers_mut().insert(
"Signature",
sig_header.parse().map_err(SignError::NewHeader)?,
);
Ok(request)
}
}
}