rocket_webhook/webhooks/built_in/
slack.rs1use hmac::Hmac;
2use rocket::{Request, data::Outcome, http::Status, outcome::try_outcome, tokio::io::AsyncRead};
3use sha2::Sha256;
4use zeroize::Zeroizing;
5
6use crate::{
7 WebhookError,
8 webhooks::{Webhook, interface::hmac::WebhookHmac},
9};
10
11pub struct SlackWebhook {
19 secret_key: Zeroizing<Vec<u8>>,
20}
21
22impl SlackWebhook {
23 pub fn with_secret(secret_key: impl Into<Vec<u8>>) -> Self {
25 Self {
26 secret_key: Zeroizing::new(secret_key.into()),
27 }
28 }
29}
30
31impl Webhook for SlackWebhook {
32 async fn validate_body(
33 &self,
34 req: &Request<'_>,
35 body: impl AsyncRead + Unpin + Send + Sync,
36 time_bounds: (u32, u32),
37 ) -> Outcome<'_, Vec<u8>, WebhookError> {
38 self.validate_with_hmac(req, body, time_bounds).await
39 }
40}
41
42impl WebhookHmac for SlackWebhook {
43 type MAC = Hmac<Sha256>;
44
45 fn secret_key(&self) -> &[u8] {
46 &self.secret_key
47 }
48
49 fn expected_signatures(&self, req: &Request<'_>) -> Outcome<'_, Vec<Vec<u8>>, WebhookError> {
50 let sig_header = try_outcome!(self.get_header(req, "X-Slack-Signature", Some("v0=")));
51 match hex::decode(sig_header) {
52 Ok(bytes) => Outcome::Success(vec![bytes]),
53 Err(_) => Outcome::Error((
54 Status::BadRequest,
55 WebhookError::InvalidHeader(format!(
56 "X-Slack-Signature header was not valid hex: '{sig_header}'"
57 )),
58 )),
59 }
60 }
61
62 fn body_prefix(
63 &self,
64 req: &Request<'_>,
65 time_bounds: (u32, u32),
66 ) -> Outcome<'_, Option<Vec<u8>>, WebhookError> {
67 let timestamp = try_outcome!(self.get_header(req, "X-Slack-Request-Timestamp", None));
68 try_outcome!(self.validate_timestamp(timestamp, time_bounds));
69
70 let prefix = [b"v0:", timestamp.as_bytes(), b":"].concat();
71 Outcome::Success(Some(prefix))
72 }
73}