http-signature-normalization-actix 0.3.0-alpha.2

An HTTP Signatures library that leaves the signing to you
Documentation
//! Types for verifying requests with Actix Web

use actix_web::{
    dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform},
    http::StatusCode,
    Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
};
use futures::future::{err, ok, ready, Ready};
use std::{
    cell::RefCell,
    future::Future,
    pin::Pin,
    rc::Rc,
    task::{Context, Poll},
};

use crate::{Config, SignatureVerify};

#[derive(Copy, Clone, Debug)]
/// A marker type that can be used to guard routes when the signature middleware is set to
/// 'optional'
pub struct SignatureVerified;

#[derive(Clone, Debug)]
/// The Verify signature middleware
///
/// ```rust,ignore
/// let middleware = VerifySignature::new(MyVerifier::new(), Config::default())
///     .authorization()
///     .optional();
///
/// HttpServer::new(move || {
///     App::new()
///         .wrap(middleware.clone())
///         .route("/protected", web::post().to(|_: SignatureVerified| "Verified Authorization Header"))
///         .route("/unprotected", web::post().to(|| "No verification required"))
/// })
/// ```
pub struct VerifySignature<T>(T, Config, HeaderKind, bool);

#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct VerifyMiddleware<T, S>(Rc<RefCell<S>>, Config, HeaderKind, bool, T);

#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
enum HeaderKind {
    Authorization,
    Signature,
}

#[derive(Clone, Debug, thiserror::Error)]
#[error("Failed to verify http signature")]
#[doc(hidden)]
pub struct VerifyError;

impl<T> VerifySignature<T>
where
    T: SignatureVerify,
{
    /// Create a new middleware for verifying HTTP Signatures. A type implementing
    /// [`SignatureVerify`] is required, as well as a Config
    ///
    /// By default, this middleware expects to verify Signature headers, and requires the presence
    /// of the header
    pub fn new(verify_signature: T, config: Config) -> Self {
        VerifySignature(verify_signature, config, HeaderKind::Signature, true)
    }

    /// Verify Authorization headers instead of Signature headers
    pub fn authorization(self) -> Self {
        VerifySignature(self.0, self.1, HeaderKind::Authorization, self.3)
    }

    /// Mark the presence of a Signature or Authorization header as optional
    ///
    /// If a header is present, it will be verified, but if there is not one present, the request
    /// is passed through. This can be used to set a global middleware, and then guard each route
    /// handler with the [`SignatureVerified`] type.
    pub fn optional(self) -> Self {
        VerifySignature(self.0, self.1, self.2, false)
    }
}

impl<T, S> VerifyMiddleware<T, S>
where
    T: SignatureVerify + 'static,
    T::Future: 'static,
    S: Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error> + 'static,
{
    fn handle(
        &mut self,
        req: ServiceRequest,
    ) -> Pin<Box<dyn Future<Output = Result<ServiceResponse<Body>, Error>>>> {
        let res = self.1.begin_verify(
            req.method(),
            req.uri().path_and_query(),
            req.headers().clone(),
        );

        let unverified = match res {
            Ok(unverified) => unverified,
            Err(_) => return Box::pin(err(VerifyError.into())),
        };

        let algorithm = unverified.algorithm().map(|a| a.clone());
        let key_id = unverified.key_id().to_owned();

        let service = self.0.clone();

        let fut = unverified.verify(|signature, signing_string| {
            self.4
                .signature_verify(algorithm, &key_id, signature, signing_string)
        });

        Box::pin(async move {
            let verified = fut.await?;

            if verified {
                req.extensions_mut().insert(SignatureVerified);
                service.borrow_mut().call(req).await
            } else {
                Err(VerifyError.into())
            }
        })
    }
}

impl HeaderKind {
    pub fn is_authorization(&self) -> bool {
        HeaderKind::Authorization == *self
    }

    pub fn is_signature(&self) -> bool {
        HeaderKind::Signature == *self
    }
}

impl FromRequest for SignatureVerified {
    type Error = VerifyError;
    type Future = Ready<Result<Self, Self::Error>>;
    type Config = ();

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        ready(
            req.extensions()
                .get::<Self>()
                .map(|s| *s)
                .ok_or(VerifyError),
        )
    }
}

impl<T, S> Transform<S> for VerifySignature<T>
where
    T: SignatureVerify + Clone + 'static,
    S: Service<
            Request = ServiceRequest,
            Response = ServiceResponse<Body>,
            Error = actix_web::Error,
        > + 'static,
    S::Error: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<Body>;
    type Error = actix_web::Error;
    type Transform = VerifyMiddleware<T, S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(VerifyMiddleware(
            Rc::new(RefCell::new(service)),
            self.1.clone(),
            self.2,
            self.3,
            self.0.clone(),
        ))
    }
}

impl<T, S> Service for VerifyMiddleware<T, S>
where
    T: SignatureVerify + Clone + 'static,
    S: Service<
            Request = ServiceRequest,
            Response = ServiceResponse<Body>,
            Error = actix_web::Error,
        > + 'static,
    S::Error: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<Body>;
    type Error = actix_web::Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
        self.0.borrow_mut().poll_ready(cx)
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        let authorization = req.headers().get("Authorization").is_some();
        let signature = req.headers().get("Signature").is_some();

        if authorization || signature {
            if self.2.is_authorization() && authorization {
                return self.handle(req);
            }

            if self.2.is_signature() && signature {
                return self.handle(req);
            }

            Box::pin(err(VerifyError.into()))
        } else if self.3 {
            Box::pin(self.0.borrow_mut().call(req))
        } else {
            Box::pin(err(VerifyError.into()))
        }
    }
}

impl ResponseError for VerifyError {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::BadRequest().finish()
    }
}