paddle_rust_sdk/
webhooks.rs1use std::num::ParseIntError;
4use std::str::FromStr;
5
6use chrono::{prelude::*, Duration};
7use hmac::{Hmac, Mac};
8use sha2::Sha256;
9
10use crate::error::{Error, SignatureError};
11
12type HmacSha256 = Hmac<Sha256>;
13
14pub struct MaximumVariance(pub Option<Duration>);
20
21impl MaximumVariance {
22 pub fn seconds(seconds: u64) -> Self {
23 Self(Some(Duration::seconds(seconds as i64)))
24 }
25}
26
27impl Default for MaximumVariance {
28 fn default() -> Self {
30 Self(Some(Duration::seconds(5)))
31 }
32}
33
34pub struct Signature {
35 timestamp: DateTime<Utc>,
36 signature: Vec<u8>,
37}
38
39impl Signature {
40 pub fn verify(
41 &self,
42 request_body: impl AsRef<str>,
43 key: impl AsRef<str>,
44 maximum_variance: MaximumVariance,
45 ) -> Result<(), Error> {
46 if let Some(maximum_variance) = maximum_variance.0 {
47 if Utc::now() > self.timestamp + maximum_variance {
48 return Err(Error::PaddleSignature(SignatureError::MaxVarianceExceeded(
49 maximum_variance,
50 )));
51 }
52 }
53
54 let signed_payload = format!("{}:{}", self.timestamp.format("%s"), request_body.as_ref());
55
56 let mut mac = HmacSha256::new_from_slice(key.as_ref().as_bytes())
57 .expect("HMAC can take key of any size");
58
59 mac.update(signed_payload.as_bytes());
60 mac.verify_slice(&self.signature)?;
61
62 Ok(())
63 }
64}
65
66impl FromStr for Signature {
67 type Err = crate::Error;
68
69 fn from_str(signature: &str) -> Result<Self, Self::Err> {
70 if signature.is_empty() {
73 return Err(Error::PaddleSignature(SignatureError::Empty));
74 }
75
76 let signature_parts = signature.split(';').collect::<Vec<_>>();
77
78 if signature_parts.len() != 2 {
79 return Err(Error::PaddleSignature(SignatureError::InvalidFormat));
80 }
81
82 let mut timestamp = None;
83 let mut signature = None;
84
85 for part in signature_parts {
86 let key_value = part.split('=').collect::<Vec<_>>();
87
88 if key_value.len() != 2 {
89 return Err(Error::PaddleSignature(SignatureError::InvalidPartFormat));
90 }
91
92 if key_value[0] == "ts" {
93 timestamp = DateTime::from_timestamp(key_value[1].parse()?, 0);
94 }
95
96 if key_value[0] == "h1" {
97 signature = Some(key_value[1].to_string());
98 }
99 }
100
101 let Some((timestamp, signature)) = timestamp.zip(signature) else {
102 return Err(Error::PaddleSignature(SignatureError::InvalidFormat));
103 };
104
105 Ok(Self {
106 timestamp,
107 signature: decode_hex(&signature)?,
108 })
109 }
110}
111
112fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
113 (0..s.len())
114 .step_by(2)
115 .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
116 .collect()
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn parsing_correctly() {
125 let signature_str =
126 "ts=1671552777;h1=eb4d0dc8853be92b7f063b9f3ba5233eb920a09459b6e6b2c26705b4364db151";
127
128 let sig: Signature = signature_str.parse().expect("To parse correctly");
129 }
130
131 #[test]
132 fn malformed_parameters() {
133 let signature_str =
134 "ts=1671552777h1=eb4d0dc8853be92b7f063b9f3ba5233eb920a09459b6e6b2c26705b4364db151";
135 assert!(signature_str.parse::<Signature>().is_err());
136
137 let signature_str =
138 "ts=1671552a777;h1=eb4d0dc8853be92b7f063b9f3ba5233eb920a09459b6e6b2c26705b4364db151";
139 assert!(signature_str.parse::<Signature>().is_err());
140 }
141}