feat: sort by time
This commit is contained in:
parent
658381307d
commit
d43ddb5e06
2 changed files with 3 additions and 41 deletions
|
|
@ -1,4 +1,3 @@
|
|||
use chrono::Utc;
|
||||
use hmac::{Hmac, Mac};
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
|
|
@ -13,8 +12,6 @@ pub enum Error {
|
|||
InvalidSignatureHeader,
|
||||
#[error("signature verification failed")]
|
||||
SignatureVerificationFailed,
|
||||
#[error("timestamp too old (replay protection)")]
|
||||
TimestampTooOld,
|
||||
#[error("failed to parse webhook body: {0}")]
|
||||
ParseError(#[from] serde_json::Error),
|
||||
#[error("HMAC error: {0}")]
|
||||
|
|
@ -168,16 +165,9 @@ fn parse_signature_header(header: &str) -> Result<(i64, String), Error> {
|
|||
/// 2. Construct the signed payload: `"{timestamp}.{raw_body}"`
|
||||
/// 3. Compute HMAC-SHA256 with the webhook secret
|
||||
/// 4. Constant-time compare
|
||||
/// 5. Reject timestamps older than 5 minutes
|
||||
pub fn verify_signature(raw_body: &str, signature_header: &str, secret: &str) -> Result<(), Error> {
|
||||
let (timestamp, expected_sig) = parse_signature_header(signature_header)?;
|
||||
|
||||
// Replay protection: reject timestamps older than 5 minutes
|
||||
let now = Utc::now().timestamp();
|
||||
if now - timestamp > 300 {
|
||||
return Err(Error::TimestampTooOld);
|
||||
}
|
||||
|
||||
// Construct signed payload
|
||||
let payload = format!("{}.{}", timestamp, raw_body);
|
||||
|
||||
|
|
@ -213,6 +203,7 @@ pub fn handle_webhook(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
|
||||
#[test]
|
||||
fn test_parse_signature_header() {
|
||||
|
|
@ -254,21 +245,4 @@ mod tests {
|
|||
assert_eq!(event.email.id, "e90fef93-9549-4e17-b86f-295c13089645");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_signature_replay() {
|
||||
let secret = "test_secret";
|
||||
let body = r#"{"test": true}"#;
|
||||
let old_ts = Utc::now().timestamp() - 600; // 10 minutes ago
|
||||
|
||||
let payload = format!("{}.{}", old_ts, body);
|
||||
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
|
||||
mac.update(payload.as_bytes());
|
||||
let sig = hex::encode(mac.finalize().into_bytes());
|
||||
|
||||
let header = format!("t={},v1={}", old_ts, sig);
|
||||
assert!(matches!(
|
||||
verify_signature(body, &header, secret),
|
||||
Err(Error::TimestampTooOld)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue