use hmac::{Hmac, Mac};
use http::Request;
use sha2::Sha256;
use thiserror::Error;
use tower::filter::Predicate;
#[derive(Debug, Clone)]
pub struct HmacQueryParamValidator {
pub key: String,
}
#[derive(Error, Debug)]
pub enum HmacQueryParamError {
#[error("Request did not have a query string")]
NoQueryString,
#[error("Request did not have an HMAC query param")]
QueryStringButNoHmac,
#[error("Hash key not appropriately set")]
HashKeyNotSet,
#[error("Failed decoding HMAC as hex string")]
HexDecodingError,
#[error("Computed hash did not match provided hash")]
HashVerificationFailed,
}
impl<T> Predicate<Request<T>> for HmacQueryParamValidator {
type Request = http::Request<T>;
fn check(&mut self, request: Request<T>) -> Result<Self::Request, tower::BoxError> {
let query = request.uri().query().ok_or(HmacQueryParamError::NoQueryString)?;
let (hmac, params): (Vec<_>, Vec<_>) =
form_urlencoded::parse(query.as_bytes()).partition(|(key, _)| key == "hmac");
let hmac = &hmac.first().ok_or(HmacQueryParamError::QueryStringButNoHmac)?.1;
let query_string_without_hmac = form_urlencoded::Serializer::new(String::new())
.extend_pairs(params)
.finish();
let mut hasher =
Hmac::<Sha256>::new_from_slice(self.key.as_bytes()).map_err(|_| HmacQueryParamError::HashKeyNotSet)?;
hasher.update(&query_string_without_hmac.into_bytes());
let hmac_bytes = hex::decode(hmac.as_bytes()).map_err(|_| HmacQueryParamError::HexDecodingError)?;
hasher
.verify(hmac_bytes.as_slice().into())
.map_err(|_| HmacQueryParamError::HashVerificationFailed)?;
Ok(request)
}
}