rocket_webhook/webhooks/built_in/
discord.rs1use hex::FromHexError;
2use rocket::{data::Outcome, http::Status, outcome::try_outcome, tokio::io::AsyncRead};
3use tokio_util::bytes::{Bytes, BytesMut};
4
5use crate::{
6 WebhookError,
7 webhooks::{
8 Webhook,
9 interface::public_key::{WebhookPublicKey, algorithms::ed25519::Ed25519},
10 },
11};
12
13pub struct DiscordWebhook {
20 public_key: Bytes,
21}
22
23impl DiscordWebhook {
24 pub fn with_public_key(public_key: impl AsRef<str>) -> Result<Self, FromHexError> {
26 let public_key = Bytes::from(hex::decode(public_key.as_ref())?);
27 Ok(Self { public_key })
28 }
29}
30
31impl Webhook for DiscordWebhook {
32 async fn validate_body(
33 &self,
34 req: &rocket::Request<'_>,
35 body: impl AsyncRead + Unpin + Send + Sync,
36 time_bounds: (u32, u32),
37 ) -> Outcome<'_, Vec<u8>, WebhookError> {
38 self.validate_with_public_key(req, body, time_bounds).await
39 }
40}
41
42impl WebhookPublicKey for DiscordWebhook {
43 type ALG = Ed25519;
44
45 async fn public_key(&self, _req: &rocket::Request<'_>) -> Outcome<'_, Bytes, WebhookError> {
46 Outcome::Success(self.public_key.clone())
47 }
48
49 fn expected_signature(&self, req: &rocket::Request<'_>) -> Outcome<'_, Vec<u8>, WebhookError> {
50 let sig_header = try_outcome!(self.get_header(req, "X-Signature-Ed25519", None));
51 match hex::decode(sig_header) {
52 Ok(bytes) => Outcome::Success(bytes),
53 Err(_) => Outcome::Error((
54 Status::BadRequest,
55 WebhookError::InvalidHeader(format!(
56 "X-Signature-Ed25519 header was not valid hex: '{sig_header}'"
57 )),
58 )),
59 }
60 }
61
62 fn message_to_verify(
63 &self,
64 req: &rocket::Request<'_>,
65 body: &Bytes,
66 time_bounds: (u32, u32),
67 ) -> Outcome<'_, Bytes, WebhookError> {
68 let timestamp = try_outcome!(self.get_header(req, "X-Signature-Timestamp", None));
69 try_outcome!(self.validate_timestamp(timestamp, time_bounds));
70
71 let mut timestamp_and_body = BytesMut::with_capacity(timestamp.len() + body.len());
72 timestamp_and_body.extend_from_slice(timestamp.as_bytes());
73 timestamp_and_body.extend_from_slice(body);
74
75 Outcome::Success(timestamp_and_body.freeze())
76 }
77}