use std::collections::HashMap;
mod sentry;
mod slack;
mod standard;
pub use sentry::SentryVerifier;
pub use slack::SlackVerifier;
pub use standard::StandardVerifier;
#[derive(Debug, thiserror::Error)]
pub enum WebhookError {
#[error("missing required header: {0}")]
MissingHeader(String),
#[error("malformed header {0}: {1}")]
MalformedHeader(String, String),
#[error("invalid signature")]
InvalidSignature,
#[error("timestamp out of tolerance")]
TimestampOutOfTolerance,
#[error("unsupported scheme: {0}")]
UnsupportedScheme(String),
}
#[derive(Debug, Clone, Default)]
pub struct Verified {
pub idempotency_key: Option<String>,
pub event_type: Option<String>,
pub timestamp: Option<i64>,
}
pub trait Verifier {
fn verify(
&self,
secret: &[u8],
headers: &HashMap<String, String>,
body: &[u8],
) -> Result<Verified, WebhookError>;
}
pub(crate) fn ct_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}
pub(crate) fn check_timestamp(ts: i64, tolerance_secs: i64) -> Result<(), WebhookError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
if (now - ts).abs() > tolerance_secs {
return Err(WebhookError::TimestampOutOfTolerance);
}
Ok(())
}