bsv-payment-actix-middleware 0.1.0

BSV payment middleware for Actix-web, wire-compatible with the TypeScript payment-express-middleware
Documentation
//! Request extractors for authenticated identity and payment info.

use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpMessage, HttpRequest};
use futures_util::future::{ok, Ready};

use crate::error::PaymentError;
use crate::types::PaymentInfo;

/// Identity of the authenticated caller, extracted from request extensions.
///
/// The auth middleware inserts this into request extensions before the payment
/// middleware runs. If missing, the payment middleware was misconfigured
/// (running without auth middleware).
#[derive(Clone, Debug)]
pub struct AuthIdentity {
    /// The public identity key of the authenticated caller.
    pub identity_key: String,
}

impl FromRequest for AuthIdentity {
    type Error = actix_web::Error;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
        match req.extensions().get::<AuthIdentity>() {
            Some(id) => ok(id.clone()),
            None => futures_util::future::err(PaymentError::ServerMisconfigured.into()),
        }
    }
}

impl FromRequest for PaymentInfo {
    type Error = actix_web::Error;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
        match req.extensions().get::<PaymentInfo>() {
            Some(info) => ok(info.clone()),
            None => futures_util::future::err(PaymentError::ServerMisconfigured.into()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::test::TestRequest;

    #[actix_rt::test]
    async fn auth_identity_extraction_succeeds_when_present() {
        let identity = AuthIdentity {
            identity_key: "test-key-123".to_string(),
        };
        let req = TestRequest::default().to_http_request();
        req.extensions_mut().insert(identity);

        let (_, mut payload) = TestRequest::default().to_http_parts();
        let extracted = AuthIdentity::from_request(&req, &mut payload).await;
        assert!(extracted.is_ok());
        assert_eq!(extracted.unwrap().identity_key, "test-key-123");
    }

    #[actix_rt::test]
    async fn auth_identity_extraction_fails_when_missing() {
        let req = TestRequest::default().to_http_request();
        let (_, mut payload) = TestRequest::default().to_http_parts();
        let result = AuthIdentity::from_request(&req, &mut payload).await;
        assert!(result.is_err());
    }

    #[actix_rt::test]
    async fn payment_info_extraction_succeeds_when_present() {
        let info = PaymentInfo {
            satoshis_paid: 200,
            accepted: Some(true),
            tx: None,
        };
        let req = TestRequest::default().to_http_request();
        req.extensions_mut().insert(info);

        let (_, mut payload) = TestRequest::default().to_http_parts();
        let extracted = PaymentInfo::from_request(&req, &mut payload).await;
        assert!(extracted.is_ok());
        let extracted = extracted.unwrap();
        assert_eq!(extracted.satoshis_paid, 200);
        assert_eq!(extracted.accepted, Some(true));
    }

    #[actix_rt::test]
    async fn payment_info_extraction_fails_when_missing() {
        let req = TestRequest::default().to_http_request();
        let (_, mut payload) = TestRequest::default().to_http_parts();
        let result = PaymentInfo::from_request(&req, &mut payload).await;
        assert!(result.is_err());
    }
}