use bon::Builder;
use hmac::Hmac;
use rocket::{Request, data::Outcome, http::Status, tokio::io::AsyncRead};
use sha2::Sha256;
use zeroize::Zeroizing;
use crate::{
WebhookError,
webhooks::{Webhook, interface::hmac::WebhookHmac},
};
#[derive(Builder)]
pub struct Hmac256Webhook {
#[builder(with = |secret: impl Into<Vec<u8>>| Zeroizing::new(secret.into()))]
secret: Zeroizing<Vec<u8>>,
expected_signatures: fn(req: &Request<'_>) -> Option<Vec<Vec<u8>>>,
body_prefix:
Option<fn(req: &Request<'_>, time_bounds: (u32, u32)) -> Result<Vec<u8>, WebhookError>>,
body_suffix:
Option<fn(req: &Request<'_>, time_bounds: (u32, u32)) -> Result<Vec<u8>, WebhookError>>,
}
impl Webhook for Hmac256Webhook {
async fn validate_body(
&self,
req: &Request<'_>,
body: impl AsyncRead + Unpin + Send + Sync,
time_bounds: (u32, u32),
) -> Outcome<'_, Vec<u8>, WebhookError> {
self.validate_with_hmac(req, body, time_bounds).await
}
}
impl WebhookHmac for Hmac256Webhook {
type MAC = Hmac<Sha256>;
fn secret_key(&self) -> &[u8] {
&self.secret
}
fn expected_signatures(&self, req: &Request<'_>) -> Outcome<'_, Vec<Vec<u8>>, WebhookError> {
match (self.expected_signatures)(req) {
Some(signatures) => Outcome::Success(signatures),
None => Outcome::Error((
Status::BadRequest,
WebhookError::Signature("Valid signature(s) not provided in request".into()),
)),
}
}
fn body_prefix(
&self,
req: &Request<'_>,
time_bounds: (u32, u32),
) -> Outcome<'_, Option<Vec<u8>>, WebhookError> {
if let Some(prefix_fn) = self.body_prefix {
match (prefix_fn)(req, time_bounds) {
Ok(prefix) => Outcome::Success(Some(prefix)),
Err(err) => Outcome::Error((Status::BadRequest, err)),
}
} else {
Outcome::Success(None)
}
}
fn body_suffix(
&self,
req: &Request<'_>,
time_bounds: (u32, u32),
) -> Outcome<'_, Option<Vec<u8>>, WebhookError> {
if let Some(suffix_fn) = self.body_suffix {
match (suffix_fn)(req, time_bounds) {
Ok(suffix) => Outcome::Success(Some(suffix)),
Err(err) => Outcome::Error((Status::BadRequest, err)),
}
} else {
Outcome::Success(None)
}
}
}