rocket_webhook/webhooks/built_in/
shopify.rs1use base64::{Engine, prelude::BASE64_STANDARD};
2use hmac::Hmac;
3use rocket::{Request, data::Outcome, http::Status, outcome::try_outcome};
4use sha2::Sha256;
5use zeroize::Zeroizing;
6
7use crate::{
8 WebhookError,
9 webhooks::{Webhook, interface::hmac::WebhookHmac},
10};
11
12pub struct ShopifyWebhook {
17 secret_key: Zeroizing<Vec<u8>>,
18}
19
20impl ShopifyWebhook {
21 pub fn with_secret(secret_key: impl Into<Vec<u8>>) -> Self {
23 Self {
24 secret_key: Zeroizing::new(secret_key.into()),
25 }
26 }
27}
28
29impl Webhook for ShopifyWebhook {
30 async fn validate_body(
31 &self,
32 req: &Request<'_>,
33 body: impl rocket::tokio::io::AsyncRead + Unpin + Send + Sync,
34 time_bounds: (u32, u32),
35 ) -> Outcome<'_, Vec<u8>, WebhookError> {
36 self.validate_with_hmac(req, body, time_bounds).await
37 }
38}
39
40impl WebhookHmac for ShopifyWebhook {
41 type MAC = Hmac<Sha256>;
42
43 fn secret_key(&self) -> &[u8] {
44 &self.secret_key
45 }
46
47 fn expected_signatures(&self, req: &Request<'_>) -> Outcome<'_, Vec<Vec<u8>>, WebhookError> {
48 let sig_header = try_outcome!(self.get_header(req, "X-Shopify-Hmac-Sha256", None));
49 match BASE64_STANDARD.decode(sig_header) {
50 Ok(bytes) => Outcome::Success(vec![bytes]),
51 Err(_) => Outcome::Error((
52 Status::BadRequest,
53 WebhookError::InvalidHeader(format!(
54 "X-Shopify-Hmac-Sha256 header was not valid base64: '{sig_header}'"
55 )),
56 )),
57 }
58 }
59}