@@ -28,6 +28,18 @@ def expect_response(response, expected_type, expected_body_content = nil)
28
28
expect ( response . body ) . to include ( expected_body_content ) if expected_body_content
29
29
end
30
30
31
+ def expect_json_auth_failure ( response , expected_request_id : nil )
32
+ expect ( response ) . to be_a ( Net ::HTTPUnauthorized )
33
+ expect ( response . content_type ) . to eq ( "application/json" )
34
+
35
+ body = parse_json_response ( response )
36
+ expect ( body [ "error" ] ) . to eq ( "authentication_failed" )
37
+ expect ( body [ "message" ] ) . to eq ( "authentication failed" )
38
+ expect ( body ) . to have_key ( "request_id" )
39
+ expect ( body [ "request_id" ] ) . to be_a ( String )
40
+ expect ( body [ "request_id" ] ) . to eq ( expected_request_id ) if expected_request_id
41
+ end
42
+
31
43
def parse_json_response ( response )
32
44
JSON . parse ( response . body )
33
45
end
@@ -140,13 +152,13 @@ def expired_unix_timestamp(seconds_ago = 600)
140
152
headers = json_headers ( "X-Hub-Signature-256" => "sha256=invalidsignature" )
141
153
response = make_request ( :post , "/webhooks/github" , payload . to_json , headers )
142
154
143
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
155
+ expect_json_auth_failure ( response )
144
156
end
145
157
146
158
it "receives a POST request but there is no HMAC related header" do
147
159
payload = { action : "push" , repository : { name : "test-repo" } }
148
160
response = make_request ( :post , "/webhooks/github" , payload . to_json , json_headers )
149
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
161
+ expect_json_auth_failure ( response )
150
162
end
151
163
152
164
it "receives a POST request but it uses the wrong algo" do
@@ -155,7 +167,7 @@ def expired_unix_timestamp(seconds_ago = 600)
155
167
signature = generate_hmac_signature ( json_payload , FAKE_HMAC_SECRET , "sha512" , "sha512=" )
156
168
headers = json_headers ( "X-Hub-Signature-256" => signature )
157
169
response = make_request ( :post , "/webhooks/github" , json_payload , headers )
158
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
170
+ expect_json_auth_failure ( response )
159
171
end
160
172
161
173
it "successfully processes a valid POST request with HMAC signature" do
@@ -212,7 +224,7 @@ def expired_unix_timestamp(seconds_ago = 600)
212
224
signature = generate_hmac_with_timestamp ( json_payload , "bad-hmac-secret" , timestamp )
213
225
headers = json_headers ( "X-HMAC-Signature" => signature , "X-HMAC-Timestamp" => timestamp )
214
226
response = make_request ( :post , "/webhooks/hmac_with_timestamp" , json_payload , headers )
215
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
227
+ expect_json_auth_failure ( response )
216
228
end
217
229
218
230
it "fails due to missing timestamp header" do
@@ -221,7 +233,7 @@ def expired_unix_timestamp(seconds_ago = 600)
221
233
signature = generate_hmac_with_timestamp ( json_payload , FAKE_ALT_HMAC_SECRET , current_timestamp )
222
234
headers = json_headers ( "X-HMAC-Signature" => signature )
223
235
response = make_request ( :post , "/webhooks/hmac_with_timestamp" , json_payload , headers )
224
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
236
+ expect_json_auth_failure ( response )
225
237
end
226
238
227
239
it "fails due to invalid timestamp format" do
@@ -231,7 +243,7 @@ def expired_unix_timestamp(seconds_ago = 600)
231
243
signature = generate_hmac_with_timestamp ( json_payload , FAKE_ALT_HMAC_SECRET , invalid_timestamp )
232
244
headers = json_headers ( "X-HMAC-Signature" => signature , "X-HMAC-Timestamp" => invalid_timestamp )
233
245
response = make_request ( :post , "/webhooks/hmac_with_timestamp" , json_payload , headers )
234
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
246
+ expect_json_auth_failure ( response )
235
247
end
236
248
237
249
it "rejects request with timestamp manipulation attack" do
@@ -244,7 +256,7 @@ def expired_unix_timestamp(seconds_ago = 600)
244
256
signature = generate_hmac_with_timestamp ( json_payload , FAKE_ALT_HMAC_SECRET , original_timestamp )
245
257
headers = json_headers ( "X-HMAC-Signature" => signature , "X-HMAC-Timestamp" => manipulated_timestamp )
246
258
response = make_request ( :post , "/webhooks/hmac_with_timestamp" , json_payload , headers )
247
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
259
+ expect_json_auth_failure ( response )
248
260
end
249
261
250
262
it "fails because the timestamp is too old" do
@@ -254,7 +266,7 @@ def expired_unix_timestamp(seconds_ago = 600)
254
266
signature = generate_hmac_with_timestamp ( json_payload , FAKE_ALT_HMAC_SECRET , expired_ts )
255
267
headers = json_headers ( "X-HMAC-Signature" => signature , "X-HMAC-Timestamp" => expired_ts )
256
268
response = make_request ( :post , "/webhooks/hmac_with_timestamp" , json_payload , headers )
257
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
269
+ expect_json_auth_failure ( response )
258
270
end
259
271
260
272
it "fails because the wrong HMAC algorithm is used" do
@@ -265,7 +277,7 @@ def expired_unix_timestamp(seconds_ago = 600)
265
277
signature = signature . gsub ( "sha256=" , "sha512=" )
266
278
headers = json_headers ( "X-HMAC-Signature" => signature , "X-HMAC-Timestamp" => timestamp )
267
279
response = make_request ( :post , "/webhooks/hmac_with_timestamp" , json_payload , headers )
268
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
280
+ expect_json_auth_failure ( response )
269
281
end
270
282
end
271
283
@@ -289,7 +301,7 @@ def expired_unix_timestamp(seconds_ago = 600)
289
301
signature = generate_slack_signature ( json_payload , FAKE_ALT_HMAC_SECRET , expired_ts )
290
302
headers = json_headers ( "Signature-256" => signature , "X-Timestamp" => expired_ts )
291
303
response = make_request ( :post , "/webhooks/slack" , json_payload , headers )
292
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
304
+ expect_json_auth_failure ( response )
293
305
end
294
306
295
307
it "rejects request with missing timestamp header" do
@@ -299,7 +311,7 @@ def expired_unix_timestamp(seconds_ago = 600)
299
311
signature = generate_slack_signature ( json_payload , FAKE_ALT_HMAC_SECRET , timestamp )
300
312
headers = json_headers ( "Signature-256" => signature )
301
313
response = make_request ( :post , "/webhooks/slack" , json_payload , headers )
302
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
314
+ expect_json_auth_failure ( response )
303
315
end
304
316
305
317
it "rejects request with invalid timestamp format" do
@@ -309,7 +321,7 @@ def expired_unix_timestamp(seconds_ago = 600)
309
321
signature = generate_slack_signature ( json_payload , FAKE_ALT_HMAC_SECRET , invalid_timestamp )
310
322
headers = json_headers ( "Signature-256" => signature , "X-Timestamp" => invalid_timestamp )
311
323
response = make_request ( :post , "/webhooks/slack" , json_payload , headers )
312
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
324
+ expect_json_auth_failure ( response )
313
325
end
314
326
315
327
it "successfully processes request with ISO 8601 UTC timestamp" do
@@ -355,7 +367,7 @@ def expired_unix_timestamp(seconds_ago = 600)
355
367
signature = generate_slack_signature ( json_payload , FAKE_ALT_HMAC_SECRET , non_utc_timestamp )
356
368
headers = json_headers ( "Signature-256" => signature , "X-Timestamp" => non_utc_timestamp )
357
369
response = make_request ( :post , "/webhooks/slack" , json_payload , headers )
358
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
370
+ expect_json_auth_failure ( response )
359
371
end
360
372
361
373
it "rejects request with timestamp manipulation attack" do
@@ -368,7 +380,7 @@ def expired_unix_timestamp(seconds_ago = 600)
368
380
signature = generate_slack_signature ( json_payload , FAKE_ALT_HMAC_SECRET , original_timestamp )
369
381
headers = json_headers ( "Signature-256" => signature , "X-Timestamp" => manipulated_timestamp )
370
382
response = make_request ( :post , "/webhooks/slack" , json_payload , headers )
371
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
383
+ expect_json_auth_failure ( response )
372
384
end
373
385
end
374
386
@@ -377,7 +389,7 @@ def expired_unix_timestamp(seconds_ago = 600)
377
389
payload = { event : "user.login" , user : { id : "12345" } }
378
390
headers = json_headers ( "Authorization" => "badvalue" )
379
391
response = make_request ( :post , "/webhooks/okta" , payload . to_json , headers )
380
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
392
+ expect_json_auth_failure ( response )
381
393
end
382
394
383
395
it "successfully processes a valid POST request with shared secret" do
@@ -420,14 +432,14 @@ def expired_unix_timestamp(seconds_ago = 600)
420
432
payload = { } . to_json
421
433
headers = { "Authorization" => "Bearer wrong-secret" }
422
434
response = make_request ( :post , "/webhooks/with_custom_auth_plugin" , payload , headers )
423
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
435
+ expect_json_auth_failure ( response )
424
436
end
425
437
426
438
it "rejects requests with missing credentials using custom auth plugin" do
427
439
payload = { } . to_json
428
440
headers = { }
429
441
response = make_request ( :post , "/webhooks/with_custom_auth_plugin" , payload , headers )
430
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
442
+ expect_json_auth_failure ( response )
431
443
end
432
444
end
433
445
@@ -518,12 +530,15 @@ def expired_unix_timestamp(seconds_ago = 600)
518
530
it "sends a POST request to the /webhooks/boomtown_with_error endpoint and it explodes with a simple text error" do
519
531
payload = { boom_simple_text : true } . to_json
520
532
response = make_request ( :post , "/webhooks/boomtown_with_error" , payload , json_headers )
521
- expect_response ( response , Net ::HTTPInternalServerError , "boomtown_with_error: the payload triggered a simple text boomtown error" )
533
+ expect_response ( response , Net ::HTTPInternalServerError )
522
534
523
- body = response . body
524
- expect ( body ) . to eq ( "boomtown_with_error: the payload triggered a simple text boomtown error" )
525
- expect ( response . content_type ) . to eq ( "text/plain" )
535
+ # With JSON default format, even string errors are JSON-encoded
536
+ expect ( response . content_type ) . to eq ( "application/json" )
526
537
expect ( response . code ) . to eq ( "500" )
538
+
539
+ # The error body should be the JSON-encoded string
540
+ body = parse_json_response ( response )
541
+ expect ( body ) . to eq ( "boomtown_with_error: the payload triggered a simple text boomtown error" )
527
542
end
528
543
end
529
544
@@ -546,14 +561,14 @@ def expired_unix_timestamp(seconds_ago = 600)
546
561
json_payload = payload . to_json
547
562
headers = json_headers ( "Tailscale-Webhook-Signature" => "t=1663781880,v1=invalidsignature" )
548
563
response = make_request ( :post , "/webhooks/tailscale" , json_payload , headers )
549
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
564
+ expect_json_auth_failure ( response )
550
565
end
551
566
552
567
it "rejects request with missing signature header" do
553
568
payload = { event : "user.login" , user : { id : "12345" } }
554
569
json_payload = payload . to_json
555
570
response = make_request ( :post , "/webhooks/tailscale" , json_payload , json_headers )
556
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
571
+ expect_json_auth_failure ( response )
557
572
end
558
573
559
574
it "rejects request with wrong signature algorithm" do
@@ -565,7 +580,7 @@ def expired_unix_timestamp(seconds_ago = 600)
565
580
wrong_signature = OpenSSL ::HMAC . hexdigest ( OpenSSL ::Digest . new ( "sha1" ) , FAKE_ALT_HMAC_SECRET , signing_payload )
566
581
headers = json_headers ( "Tailscale-Webhook-Signature" => "t=#{ timestamp } ,v1=#{ wrong_signature } " )
567
582
response = make_request ( :post , "/webhooks/tailscale" , json_payload , headers )
568
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
583
+ expect_json_auth_failure ( response )
569
584
end
570
585
end
571
586
@@ -584,7 +599,7 @@ def expired_unix_timestamp(seconds_ago = 600)
584
599
payload = { } . to_json
585
600
headers = { "Content-Type" => "application/json" , "X-Forwarded-For" => "123.456.789.000" }
586
601
response = make_request ( :post , "/webhooks/ip_filtering_example" , payload , headers )
587
- expect_response ( response , Net :: HTTPUnauthorized , "authentication failed" )
602
+ expect_json_auth_failure ( response )
588
603
end
589
604
end
590
605
0 commit comments