bsv-payment-actix-middleware 0.1.0

BSV payment middleware for Actix-web, wire-compatible with the TypeScript payment-express-middleware
Documentation
//! Bridge adapter between auth-actix-middleware and payment-actix-middleware.
//!
//! Resolves the `TypeId` mismatch: the auth crate inserts `Authenticated`
//! into request extensions, but the payment crate reads `AuthIdentity`.
//! This middleware copies the identity key from one to the other.
//!
//! Enable with the `bridge` feature flag in Cargo.toml.

use std::future::{ready, Ready};
use std::rc::Rc;

use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{Error, HttpMessage};
use futures_util::future::LocalBoxFuture;
use futures_util::FutureExt;
use tracing::debug;

use bsv_auth_actix_middleware::Authenticated;

use crate::extractor::AuthIdentity;

/// Middleware that bridges `Authenticated` (from the auth crate) to
/// `AuthIdentity` (used by the payment crate).
///
/// Wrap your Actix app or scope with this middleware **between** the auth
/// middleware and the payment middleware so that the payment layer can read the
/// caller's identity without manual extension insertion.
///
/// # Example
///
/// ```rust,ignore
/// App::new()
///     .wrap(PaymentMiddlewareFactory::new(config))
///     .wrap(AuthToPaymentBridge)
///     .wrap(AuthMiddleware::new(auth_config))
/// ```
pub struct AuthToPaymentBridge;

impl<S, B> Transform<S, ServiceRequest> for AuthToPaymentBridge
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = AuthToPaymentBridgeService<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(AuthToPaymentBridgeService {
            service: Rc::new(service),
        }))
    }
}

/// The inner service half of [`AuthToPaymentBridge`].
pub struct AuthToPaymentBridgeService<S> {
    service: Rc<S>,
}

impl<S, B> Service<ServiceRequest> for AuthToPaymentBridgeService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        // Transfer identity from auth crate's Authenticated to payment crate's AuthIdentity.
        // Clone the identity key out of the immutable borrow before taking a mutable borrow
        // to avoid RefCell double-borrow panic.
        let identity_key = req
            .extensions()
            .get::<Authenticated>()
            .map(|auth| auth.identity_key.clone());

        if let Some(key) = identity_key {
            let identity = AuthIdentity { identity_key: key };
            debug!(
                identity_key = %identity.identity_key,
                "bridge: transferred auth identity to payment extensions"
            );
            req.extensions_mut().insert(identity);
        }

        let service = Rc::clone(&self.service);
        async move { service.call(req).await }.boxed_local()
    }
}