tower_webflow/
service.rs

1use crate::util::{WebflowHeaders, compare_signatures, from_request_parts};
2use axum::http::Request;
3use axum::{
4    body::{self, Body},
5    response::Response,
6};
7use std::future::Future;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10use tower::{BoxError, Service};
11
12const BODY_LIMIT: usize = 1_048_576;
13
14/// Middleware that validates the x-webflow-signature header
15#[derive(Clone)]
16pub struct WebflowService<S, Secret> {
17    pub(crate) inner: S,
18    pub secret: Secret,
19}
20
21impl<S, Secret> Service<Request<Body>> for WebflowService<S, Secret>
22where
23    S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
24    S::Future: Send + 'static,
25    S::Error: Into<BoxError>,
26    Secret: AsRef<[u8]> + Clone + Send + 'static,
27{
28    type Response = Response;
29    type Error = BoxError;
30    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
31
32    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
33        self.inner.poll_ready(cx).map_err(Into::into)
34    }
35
36    fn call(&mut self, req: Request<Body>) -> Self::Future {
37        let mut inner = self.inner.clone();
38        let secret = self.secret.clone();
39        Box::pin(async move {
40            let (mut parts, body) = req.into_parts();
41
42            let body_bytes = body::to_bytes(body, BODY_LIMIT)
43                .await
44                .map_err(Into::<BoxError>::into)?;
45
46            let body_string =
47                String::from_utf8(body_bytes.to_vec()).map_err(Into::<BoxError>::into)?;
48
49            tracing::debug!(body_string, "webflow-body");
50
51            let WebflowHeaders(signature, timestamp) = from_request_parts(&mut parts)?;
52
53            tracing::debug!(signature, "webflow-header-signature");
54            tracing::debug!(timestamp, "webflow-header-timestamp");
55
56            let message_to_verify = format!("{timestamp}:{body_string}");
57
58            if compare_signatures(&message_to_verify, secret.as_ref(), &signature) {
59                let new_req = Request::from_parts(parts, Body::from(body_bytes));
60                inner.call(new_req).await.map_err(Into::into)
61            } else {
62                tracing::warn!("Webflow signature validation failed");
63                Err("Could not verify signature".into())
64            }
65        })
66    }
67}