1use rand_chacha10::ChaCha20Rng;
2use rand_core10::{Rng, SeedableRng};
3use uselesskey_core::Factory;
4
5use crate::payload::{canonical_payload, stable_spec_bytes};
6use crate::secret::build_secret;
7use crate::signature::sign;
8use crate::{
9 DOMAIN_WEBHOOK_FIXTURE, NearMissScenario, NearMissWebhookFixture, WebhookFixture,
10 WebhookPayloadSpec, WebhookProfile,
11};
12
13pub trait WebhookFactoryExt {
15 fn webhook(
17 &self,
18 profile: WebhookProfile,
19 label: impl AsRef<str>,
20 payload_spec: WebhookPayloadSpec,
21 ) -> WebhookFixture;
22
23 fn webhook_github(
25 &self,
26 label: impl AsRef<str>,
27 payload_spec: WebhookPayloadSpec,
28 ) -> WebhookFixture;
29
30 fn webhook_stripe(
32 &self,
33 label: impl AsRef<str>,
34 payload_spec: WebhookPayloadSpec,
35 ) -> WebhookFixture;
36
37 fn webhook_slack(
39 &self,
40 label: impl AsRef<str>,
41 payload_spec: WebhookPayloadSpec,
42 ) -> WebhookFixture;
43}
44
45impl WebhookFactoryExt for Factory {
46 fn webhook(
47 &self,
48 profile: WebhookProfile,
49 label: impl AsRef<str>,
50 payload_spec: WebhookPayloadSpec,
51 ) -> WebhookFixture {
52 let label = label.as_ref();
53 let spec_bytes = stable_spec_bytes(profile, &payload_spec);
54 let cached = self.get_or_init(DOMAIN_WEBHOOK_FIXTURE, label, &spec_bytes, "good", |seed| {
55 build_fixture_from_seed(profile, label, payload_spec.clone(), seed.bytes())
56 });
57 cached.as_ref().clone()
58 }
59
60 fn webhook_github(
61 &self,
62 label: impl AsRef<str>,
63 payload_spec: WebhookPayloadSpec,
64 ) -> WebhookFixture {
65 self.webhook(WebhookProfile::GitHub, label, payload_spec)
66 }
67
68 fn webhook_stripe(
69 &self,
70 label: impl AsRef<str>,
71 payload_spec: WebhookPayloadSpec,
72 ) -> WebhookFixture {
73 self.webhook(WebhookProfile::Stripe, label, payload_spec)
74 }
75
76 fn webhook_slack(
77 &self,
78 label: impl AsRef<str>,
79 payload_spec: WebhookPayloadSpec,
80 ) -> WebhookFixture {
81 self.webhook(WebhookProfile::Slack, label, payload_spec)
82 }
83}
84
85impl WebhookFixture {
86 pub fn near_miss_stale_timestamp(&self, max_age_secs: i64) -> NearMissWebhookFixture {
88 let stale_ts = self.timestamp - max_age_secs - 1;
89 let mut f = self.with_timestamp(stale_ts);
90 f.scenario = NearMissScenario::StaleTimestamp;
91 f
92 }
93
94 pub fn near_miss_wrong_secret(&self) -> NearMissWebhookFixture {
96 let mut wrong_secret = self.secret.clone();
97 wrong_secret.push_str("_wrong");
98 let mut f = build_near_miss(
99 self.profile,
100 wrong_secret,
101 self.payload.clone(),
102 self.timestamp,
103 );
104 f.scenario = NearMissScenario::WrongSecret;
105 f
106 }
107
108 pub fn near_miss_tampered_payload(&self) -> NearMissWebhookFixture {
110 let tampered = format!("{}{}", self.payload, "\n");
111 let mut f = build_near_miss(self.profile, self.secret.clone(), tampered, self.timestamp);
112 f.scenario = NearMissScenario::TamperedPayload;
113 f
114 }
115
116 fn with_timestamp(&self, timestamp: i64) -> NearMissWebhookFixture {
117 build_near_miss(
118 self.profile,
119 self.secret.clone(),
120 self.payload.clone(),
121 timestamp,
122 )
123 }
124}
125
126fn build_near_miss(
127 profile: WebhookProfile,
128 secret: String,
129 payload: String,
130 timestamp: i64,
131) -> NearMissWebhookFixture {
132 let (headers, signature_input) = sign(profile, &secret, &payload, timestamp);
133 NearMissWebhookFixture {
134 scenario: NearMissScenario::StaleTimestamp,
135 profile,
136 secret,
137 payload,
138 headers,
139 timestamp,
140 signature_input,
141 }
142}
143
144pub(crate) fn build_fixture_from_seed(
145 profile: WebhookProfile,
146 label: &str,
147 payload_spec: WebhookPayloadSpec,
148 seed: &[u8; 32],
149) -> WebhookFixture {
150 let mut rng = ChaCha20Rng::from_seed(*seed);
151 let secret = build_secret(profile, &mut rng);
152 let timestamp = 1_700_000_000_i64 + (rng.next_u32() as i64 % 200_000_000_i64);
153 let payload = canonical_payload(profile, label, payload_spec, rng.next_u32());
154 let (headers, signature_input) = sign(profile, &secret, &payload, timestamp);
155
156 WebhookFixture {
157 profile,
158 secret,
159 payload,
160 headers,
161 timestamp,
162 signature_input,
163 }
164}