rocket_webhook/webhooks/built_in/
github.rs

1use 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
11/// # GitHub webhook
12/// Looks for hex signature in `X-Hub-Signature-256` header, with a 'sha256=' prefix
13///
14/// [GitHub docs](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries)
15pub struct GitHubWebhook {
16    secret_key: Zeroizing<Vec<u8>>,
17}
18
19impl GitHubWebhook {
20    /// Instantiate with the secret key
21    pub fn with_secret(secret_key: impl Into<Vec<u8>>) -> Self {
22        Self {
23            secret_key: Zeroizing::new(secret_key.into()),
24        }
25    }
26}
27
28impl Webhook for GitHubWebhook {
29    async fn validate_body(
30        &self,
31        req: &Request<'_>,
32        body: impl AsyncRead + Unpin + Send + Sync,
33        time_bounds: (u32, u32),
34    ) -> Outcome<'_, Vec<u8>, WebhookError> {
35        self.validate_with_hmac(req, body, time_bounds).await
36    }
37}
38
39impl WebhookHmac for GitHubWebhook {
40    type MAC = Hmac<Sha256>;
41
42    fn secret_key(&self) -> &[u8] {
43        &self.secret_key
44    }
45
46    fn expected_signatures(&self, req: &Request<'_>) -> Outcome<'_, Vec<Vec<u8>>, WebhookError> {
47        let sig_header = try_outcome!(self.get_header(req, "X-Hub-Signature-256", Some("sha256=")));
48        match hex::decode(sig_header) {
49            Ok(bytes) => Outcome::Success(vec![bytes]),
50            Err(_) => Outcome::Error((
51                Status::BadRequest,
52                WebhookError::InvalidHeader(format!(
53                    "X-Hub-Signature-256 header was not valid hex: '{sig_header}'"
54                )),
55            )),
56        }
57    }
58}