From 6a4cbd6e2a521a209806337d90ae8f7e291a534b Mon Sep 17 00:00:00 2001 From: Orkun Date: Thu, 8 Aug 2024 11:46:27 +0300 Subject: [PATCH 1/3] feat: add support for validating webhook signatures inter-768 --- README.md | 30 +++++++++++++++++++ docs/Webhook.md | 16 +++++++++++ run_checks.php | 11 +++++++ scripts/generate.sh | 2 +- src/Webhook/WebhookVerifier.php | 29 +++++++++++++++++++ template/README.mustache | 30 +++++++++++++++++++ test/WebhookVerifierTest.php | 51 +++++++++++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 docs/Webhook.md create mode 100644 src/Webhook/WebhookVerifier.php create mode 100644 test/WebhookVerifierTest.php diff --git a/README.md b/README.md index 0f198b1c..29f2a07c 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,32 @@ All URIs are relative to your region's base URL. | Europe | https://eu.api.fpjs.io | | Asia | https://ap.api.fpjs.io | +## Webhook Signing + +This SDK provides utility method for verifying the HMAC signature of the incoming webhook request. +You can use below code to verify signature: + +```php + Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader(string $header, string $data, string $secret): bool + +Verifies the HMAC signature extracted from the "fpjs-event-signature" header of the incoming request. This is a part of the webhook signing process, which is available only for enterprise customers. +If you wish to enable it, please [contact our support](https://fingerprint.com/support). + +### Required Parameters + +| Name | Type | Description | Notes | +|------------|------------|------------------------------------------------------------|-------| +| **header** | **string** | Value of the "fpjs-event-signature" header. | | +| **data** | **string** | Body of the request from which above header was extracted. | | +| **secret** | **string** | Your generated secret used to sign the request. | | diff --git a/run_checks.php b/run_checks.php index 4f5831f2..3815e5c8 100644 --- a/run_checks.php +++ b/run_checks.php @@ -57,6 +57,17 @@ exit(1); } +$webhookSecret = "secret"; +$webhookData = "data"; +$webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; +$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret); +if($isValidWebhookSign) { + fwrite(STDOUT, sprintf("\n\nVerified webhook signature\n")); +} else { + fwrite(STDERR, sprintf("\n\nWebhook signature verification failed\n")); + exit(1); +} + // Enable the deprecated ArrayAccess return type warning again if needed error_reporting(error_reporting() | E_DEPRECATED); diff --git a/scripts/generate.sh b/scripts/generate.sh index 7db1b68e..6dfff69c 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -65,7 +65,7 @@ java -jar ./bin/swagger-codegen-cli.jar generate -t ./template -l php -i ./res/f mv -f src/README.md ./README.md mv -f src/composer.json composer.json -find ./docs -type f ! -name "DecryptionKey.md" ! -name "Sealed.md" -exec rm {} + +find ./docs -type f ! -name "DecryptionKey.md" ! -name "Sealed.md" ! -name "Webhook.md" -exec rm {} + mv -f src/docs/* ./docs if [ -z "$GITHUB_ACTIONS" ]; then diff --git a/src/Webhook/WebhookVerifier.php b/src/Webhook/WebhookVerifier.php new file mode 100644 index 00000000..4d70a2a5 --- /dev/null +++ b/src/Webhook/WebhookVerifier.php @@ -0,0 +1,29 @@ +data, $this->secret); + $this->assertTrue($result, "With valid signature"); + } + + public function testWithInvalidHeader() + { + $result = WebhookVerifier::checkHeader("v2=invalid", $this->data, $this->secret); + $this->assertFalse($result, "With invalid header"); + } + + public function testWithHeaderWithoutVersion() + { + $result = WebhookVerifier::checkHeader("invalid", $this->data, $this->secret); + $this->assertFalse($result, "With header without version"); + } + + public function testWithEmptyHeader() + { + $result = WebhookVerifier::checkHeader("", $this->data, $this->secret); + $this->assertFalse($result, "With empty header"); + } + + public function testWithEmptySecret() + { + $validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; + $result = WebhookVerifier::checkHeader($validHeader, $this->data, ""); + $this->assertFalse($result, "With empty secret"); + } + + public function testWithEmptyData() + { + $validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; + $result = WebhookVerifier::checkHeader($validHeader, "", $this->secret); + $this->assertFalse($result, "With empty data"); + } +} \ No newline at end of file From 9f6f5311301392f8b9d61890ad9598d379c63e4d Mon Sep 17 00:00:00 2001 From: Orkun Date: Fri, 9 Aug 2024 11:59:46 +0300 Subject: [PATCH 2/3] refactor: check header to is valid webhook signature --- README.md | 2 +- docs/Webhook.md | 4 ++-- run_checks.php | 2 +- src/Webhook/WebhookVerifier.php | 2 +- template/README.mustache | 2 +- test/WebhookVerifierTest.php | 12 ++++++------ 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 29f2a07c..f03f829e 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ $webhookData = "data"; // Value of the "fpjs-event-signature" header. $webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; -$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret); +$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::IsValidWebhookSignature($webhookHeader, $webhookData, $webhookSecret); if(!$isValidWebhookSign) { fwrite(STDERR, sprintf("Webhook signature verification failed\n")); diff --git a/docs/Webhook.md b/docs/Webhook.md index 898e56eb..fe925023 100644 --- a/docs/Webhook.md +++ b/docs/Webhook.md @@ -1,8 +1,8 @@ # Webhook -## **CheckHeader** +## **IsValidWebhookSignature** -> Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader(string $header, string $data, string $secret): bool +> Fingerprint\ServerAPI\Webhook\WebhookVerifier::IsValidWebhookSignature(string $header, string $data, string $secret): bool Verifies the HMAC signature extracted from the "fpjs-event-signature" header of the incoming request. This is a part of the webhook signing process, which is available only for enterprise customers. If you wish to enable it, please [contact our support](https://fingerprint.com/support). diff --git a/run_checks.php b/run_checks.php index 3815e5c8..5b890e7b 100644 --- a/run_checks.php +++ b/run_checks.php @@ -60,7 +60,7 @@ $webhookSecret = "secret"; $webhookData = "data"; $webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; -$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret); +$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::IsValidWebhookSignature($webhookHeader, $webhookData, $webhookSecret); if($isValidWebhookSign) { fwrite(STDOUT, sprintf("\n\nVerified webhook signature\n")); } else { diff --git a/src/Webhook/WebhookVerifier.php b/src/Webhook/WebhookVerifier.php index 4d70a2a5..f8bb89a7 100644 --- a/src/Webhook/WebhookVerifier.php +++ b/src/Webhook/WebhookVerifier.php @@ -4,7 +4,7 @@ final class WebhookVerifier { - public static function checkHeader(string $header, string $data, string $secret): bool + public static function IsValidWebhookSignature(string $header, string $data, string $secret): bool { $signatures = explode(',', $header); foreach ($signatures as $signature) { diff --git a/template/README.mustache b/template/README.mustache index 3ccefb2d..de90ccda 100644 --- a/template/README.mustache +++ b/template/README.mustache @@ -201,7 +201,7 @@ $webhookData = "data"; // Value of the "fpjs-event-signature" header. $webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; -$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret); +$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::IsValidWebhookSignature($webhookHeader, $webhookData, $webhookSecret); if(!$isValidWebhookSign) { fwrite(STDERR, sprintf("Webhook signature verification failed\n")); diff --git a/test/WebhookVerifierTest.php b/test/WebhookVerifierTest.php index 4ae98db2..3c963bc1 100644 --- a/test/WebhookVerifierTest.php +++ b/test/WebhookVerifierTest.php @@ -13,39 +13,39 @@ class WebhookVerifierTest extends TestCase public function testWithValidSignature() { $validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; - $result = WebhookVerifier::checkHeader($validHeader, $this->data, $this->secret); + $result = WebhookVerifier::IsValidWebhookSignature($validHeader, $this->data, $this->secret); $this->assertTrue($result, "With valid signature"); } public function testWithInvalidHeader() { - $result = WebhookVerifier::checkHeader("v2=invalid", $this->data, $this->secret); + $result = WebhookVerifier::IsValidWebhookSignature("v2=invalid", $this->data, $this->secret); $this->assertFalse($result, "With invalid header"); } public function testWithHeaderWithoutVersion() { - $result = WebhookVerifier::checkHeader("invalid", $this->data, $this->secret); + $result = WebhookVerifier::IsValidWebhookSignature("invalid", $this->data, $this->secret); $this->assertFalse($result, "With header without version"); } public function testWithEmptyHeader() { - $result = WebhookVerifier::checkHeader("", $this->data, $this->secret); + $result = WebhookVerifier::IsValidWebhookSignature("", $this->data, $this->secret); $this->assertFalse($result, "With empty header"); } public function testWithEmptySecret() { $validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; - $result = WebhookVerifier::checkHeader($validHeader, $this->data, ""); + $result = WebhookVerifier::IsValidWebhookSignature($validHeader, $this->data, ""); $this->assertFalse($result, "With empty secret"); } public function testWithEmptyData() { $validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"; - $result = WebhookVerifier::checkHeader($validHeader, "", $this->secret); + $result = WebhookVerifier::IsValidWebhookSignature($validHeader, "", $this->secret); $this->assertFalse($result, "With empty data"); } } \ No newline at end of file From bb27cfcc4149bb8a580fbb52a71f9a89be1b43bd Mon Sep 17 00:00:00 2001 From: Orkun Date: Fri, 9 Aug 2024 18:59:48 +0300 Subject: [PATCH 3/3] docs(webhook): use symbol in desc --- docs/Webhook.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Webhook.md b/docs/Webhook.md index fe925023..004780dd 100644 --- a/docs/Webhook.md +++ b/docs/Webhook.md @@ -9,8 +9,8 @@ If you wish to enable it, please [contact our support](https://fingerprint.com/s ### Required Parameters -| Name | Type | Description | Notes | -|------------|------------|------------------------------------------------------------|-------| -| **header** | **string** | Value of the "fpjs-event-signature" header. | | +| Name | Type | Description | Notes | +|------------|------------|-----------------------------------------------------------|-------| +| **header** | **string** | Value of the `fpjs-event-signature` header. | | | **data** | **string** | Body of the request from which above header was extracted. | | -| **secret** | **string** | Your generated secret used to sign the request. | | +| **secret** | **string** | Your generated secret used to sign the request. | |