Skip to content

Commit 43f2740

Browse files
authored
Verify-webhook-signature (#60)
1 parent fab5edf commit 43f2740

File tree

4 files changed

+50
-3
lines changed

4 files changed

+50
-3
lines changed

src/Http/Controllers/WebhookController.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public function __construct()
3333
public function handleWebhook(Request $request): Response
3434
{
3535
$payload = json_decode($request->getContent(), true);
36+
37+
if(is_null($payload) || !isset($payload['type'])) {
38+
return new Response('Invalid payload', 400);
39+
}
40+
3641
$method = 'handle' . Str::studly(str_replace('.', '_', $payload['type']));
3742

3843
if (method_exists($this, $method)) {

src/Http/Middleware/VerifyWebhookSignature.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ class VerifyWebhookSignature
1919
public function handle(Request $request, Closure $next)
2020
{
2121
try {
22-
$headers = collect($request->headers->all())->transform(function ($item) {
23-
return $item[0];
24-
})->all();
22+
$headers = $this->getTransformedHeaders($request);
2523

2624
WebhookSignature::verify(
2725
$request->getContent(),
@@ -35,4 +33,22 @@ public function handle(Request $request, Closure $next)
3533

3634
return $next($request);
3735
}
36+
37+
/**
38+
* Transform headers to a simple associative array.
39+
* This method extracts the first value from each header and returns an array where each key is the header name and the associated value is that first header value
40+
*
41+
* @param \Illuminate\Http\Request $request
42+
* @return array
43+
*/
44+
45+
protected function getTransformedHeaders(Request $request): array
46+
{
47+
$headers = [];
48+
foreach($request->headers->all() as $key => $value) {
49+
$headers[$key] = $value[0];
50+
}
51+
52+
return $headers;
53+
}
3854
}

tests/Http/Controllers/WebhookController.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,16 @@
6363

6464
Event::assertNothingDispatched();
6565
});
66+
67+
// This function creates a new Request object with a JSON body that does not include the fields expected by your controller.
68+
function invalidWebhookRequest() {
69+
return new Illuminate\Http\Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/json'], json_encode(['invalid' => 'data']));
70+
}
71+
72+
test('it returns an error response for invalid payload', function() {
73+
$request = invalidWebhookRequest();
74+
75+
$response = (new Controller)->handleWebhook($request);
76+
77+
expect($response->getStatusCode())->toBe(400);
78+
});

tests/Http/Middleware/VerifyWebhookSignature.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,16 @@ function withSignature(Request $request, string $secret, int $timestamp): void
4646
return new Response('OK');
4747
});
4848
})->throws(AccessDeniedHttpException::class, 'Message timestamp too new');
49+
50+
test('it rejects a request with invalid signature', function () {
51+
$request = Request::create('/webhook', 'POST', [], [], [], [], json_encode(['data' => 'test']));
52+
$request->headers->set('Signature', 'invalid_signature');
53+
54+
$middleware = new VerifyWebhookSignature();
55+
56+
$this->expectException(AccessDeniedHttpException::class);
57+
58+
$middleware->handle($request, function ($req) {
59+
return new Response('OK', 200);
60+
});
61+
});

0 commit comments

Comments
 (0)