Skip to main content

fraiseql_webhooks/signature/
mod.rs

1//! Webhook signature verification.
2//!
3//! Supports 15+ webhook providers with constant-time comparison for security.
4
5pub mod generic;
6pub mod github;
7pub mod registry;
8pub mod shopify;
9pub mod stripe;
10
11// Additional providers
12pub mod discord;
13pub mod gitlab;
14pub mod lemonsqueezy;
15pub mod paddle;
16pub mod postmark;
17pub mod sendgrid;
18pub mod slack;
19pub mod twilio;
20
21pub use registry::ProviderRegistry;
22
23/// Errors produced by low-level signature verification routines.
24#[derive(Debug, thiserror::Error)]
25#[non_exhaustive]
26pub enum SignatureError {
27    /// The signature header value could not be parsed according to the provider's expected format.
28    /// For example, a GitHub signature missing the `sha256=` prefix triggers this variant.
29    #[error("Invalid signature format")]
30    InvalidFormat,
31
32    /// The computed signature did not match the value supplied in the request header.
33    #[error("Signature mismatch")]
34    Mismatch,
35
36    /// The timestamp embedded in the request is older than the configured tolerance window,
37    /// indicating a potential replay attack.
38    #[error("Timestamp expired")]
39    TimestampExpired,
40
41    /// A timestamp is required for this provider's signing scheme but was not found in the request.
42    #[error("Missing timestamp")]
43    MissingTimestamp,
44
45    /// A cryptographic operation failed (e.g., an invalid key was supplied or key parsing failed).
46    /// The inner string contains the underlying error message.
47    #[error("Crypto error: {0}")]
48    Crypto(String),
49}
50
51/// Constant-time comparison to prevent timing attacks.
52///
53/// Uses the `subtle` crate for verified constant-time operations,
54/// including length-independent comparison.
55#[must_use]
56pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
57    use subtle::ConstantTimeEq;
58    a.ct_eq(b).into()
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_constant_time_eq_equal() {
67        assert!(constant_time_eq(b"test", b"test"));
68        assert!(constant_time_eq(b"", b""));
69    }
70
71    #[test]
72    fn test_constant_time_eq_not_equal() {
73        assert!(!constant_time_eq(b"test", b"fail"));
74        assert!(!constant_time_eq(b"test", b"tes"));
75        assert!(!constant_time_eq(b"test", b""));
76    }
77}