use rocket::{
Request,
data::Outcome,
http::Status,
outcome::try_outcome,
tokio::io::{AsyncRead, AsyncReadExt},
};
use tokio_util::bytes::Bytes;
use crate::{
WebhookError,
webhooks::{Webhook, utils::body_size},
};
pub mod algorithms;
pub trait WebhookPublicKeyAlgorithm {
fn verify(public_key: &Bytes, message: &[u8], signature: &[u8]) -> Result<(), String>;
}
pub trait WebhookPublicKey: Webhook {
type ALG: WebhookPublicKeyAlgorithm;
fn public_key(
&self,
req: &Request<'_>,
) -> impl Future<Output = Outcome<'_, Bytes, WebhookError>> + Send + Sync;
fn expected_signature(&self, req: &Request<'_>) -> Outcome<'_, Vec<u8>, WebhookError>;
#[allow(unused_variables)]
fn message_to_verify(
&self,
req: &Request<'_>,
body: &Bytes,
time_bounds: (u32, u32),
) -> Outcome<'_, Bytes, WebhookError> {
Outcome::Success(body.clone())
}
fn validate_with_public_key(
&self,
req: &Request<'_>,
mut body: impl AsyncRead + Unpin + Send + Sync,
time_bounds: (u32, u32),
) -> impl Future<Output = Outcome<'_, Vec<u8>, WebhookError>> + Send + Sync
where
Self: Sync,
{
async move {
let expected_signature = try_outcome!(self.expected_signature(req));
let public_key = try_outcome!(self.public_key(req).await);
let mut raw_body = Vec::with_capacity(body_size(req.headers()).unwrap_or(512));
if let Err(e) = body.read_to_end(&mut raw_body).await {
return Outcome::Error((Status::BadRequest, WebhookError::Read(e)));
}
let raw_body = Bytes::from(raw_body);
let message = try_outcome!(self.message_to_verify(req, &raw_body, time_bounds));
if let Err(e) = Self::ALG::verify(&public_key, &message, &expected_signature) {
return Outcome::Error((Status::Unauthorized, WebhookError::Signature(e)));
}
Outcome::Success(raw_body.into())
}
}
}