Skip to content

Commit

Permalink
Add Middleware for verifying a Plain signature & fix tests (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiodekker authored Dec 11, 2024
1 parent 0b58b22 commit c51e03c
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 22 deletions.
25 changes: 10 additions & 15 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,17 @@ jobs:
matrix:
php: [ 8.1, 8.2, 8.3, 8.4 ]
laravel: [ 8, 9, 10, 11 ]
stability: [ 'prefer-lowest', 'prefer-stable' ]
include:
- laravel: 8.*
testbench: ^8.20
- laravel: 9.*
testbench: ^8.20
- laravel: 10.*
testbench: ^8.20
- laravel: 11.*
testbench: ^9.0
exclude:
- php: 8.1
laravel: 11.*
laravel: 11
- php: 8.4
laravel: 8
- php: 8.4
laravel: 9
- php: 8.4
laravel: 10

name: PHP ${{ matrix.php }} L${{ matrix.laravel }} w/ ${{ matrix.stability }}
name: PHP ${{ matrix.php }} L${{ matrix.laravel }}
steps:
- name: Checkout code
uses: actions/checkout@v3
Expand All @@ -49,8 +45,7 @@ jobs:
with:
timeout_minutes: 5
max_attempts: 5
command: |
composer update --prefer-dist --no-interaction --no-progress --${{ matrix.stability }}
command: composer update --prefer-dist --no-interaction --no-progress --prefer-stable

- name: Execute tests
run: vendor/bin/phpunit --verbose
run: vendor/bin/phpunit
17 changes: 12 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
},
"require": {
"php": "~8.1.0|~8.2.0|~8.3.0|~8.4.0",
"illuminate/http": "^8.83|^9.33|^10.0|^11.0",
"illuminate/support": "^8.83|^9.33|^10.0|^11.0"
"illuminate/http": "^8.83|^9.33|^10.0|^11.3",
"illuminate/support": "^8.83|^9.33|^10.0|^11.3"
},
"require-dev": {
"nunomaduro/larastan": "^2.0",
"orchestra/testbench": "^7.0|^8.0|^9.0",
"phpunit/phpunit": "^9.5.10",
"nunomaduro/larastan": "^1.0|^2.0",
"orchestra/testbench": "^6.0|^7.0|^8.2|^9.0",
"phpunit/phpunit": "^9.5.10|^10.1|^11.3.6",
"roave/security-advisories": "dev-latest"
},
"autoload": {
Expand All @@ -34,6 +34,13 @@
"config": {
"sort-packages": true
},
"extra": {
"laravel": {
"providers": [
"LemonSqueezy\\PlainUiComponents\\PlainUiComponentsServiceProvider"
]
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
13 changes: 13 additions & 0 deletions config/plain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

return [

/**
* Your Plain workspace's global HMAC secret.
*
* This secret can be viewed and (re)generated by Plain workspace admins in Settings → Request signing.
* This will be used to verify that request were made by Plain, and not a third party.
*/
'secret' => env('PLAIN_SECRET'),

];
32 changes: 32 additions & 0 deletions src/PlainUiComponentsServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace LemonSqueezy\PlainUiComponents;

use Illuminate\Support\ServiceProvider;

class PlainUiComponentsServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/plain.php', 'plain');
}

/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/plain.php' => config_path('plain.php'),
]);
}
}
}
23 changes: 23 additions & 0 deletions src/VerifyPlainSignatureMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace LemonSqueezy\PlainUiComponents;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Symfony\Component\HttpFoundation\Response;

class VerifyPlainSignatureMiddleware
{
/**
* Handle the incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
abort_if(empty($signature = $request->header('plain-request-signature')), 400, 'Missing webhook signature.');
abort_if(is_null($secret = Config::get('plain.secret')), 403, 'No webhook secret configured.');
abort_unless(hash_equals(hash_hmac('sha256', $request->getContent(), $secret), $signature), 403, 'Invalid signature.');

return $next($request);
}
}
11 changes: 9 additions & 2 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

namespace LemonSqueezy\PlainUiComponents\Tests;

class TestCase extends \PHPUnit\Framework\TestCase
use LemonSqueezy\PlainUiComponents\PlainUiComponentsServiceProvider;

class TestCase extends \Orchestra\Testbench\TestCase
{
//
protected function getPackageProviders($app): array
{
return [
PlainUiComponentsServiceProvider::class,
];
}
}
80 changes: 80 additions & 0 deletions tests/VerifyPlainSignatureMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace LemonSqueezy\PlainUiComponents\Tests;

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Route;
use LemonSqueezy\PlainUiComponents\VerifyPlainSignatureMiddleware;

class VerifyPlainSignatureMiddlewareTest extends TestCase
{
/** @test */
public function it_blocks_the_request_when_the_plain_signature_header_is_not_provided(): void
{
Config::set('plain.secret', 'my-plain-secret');

$called = false;
Route::middleware(VerifyPlainSignatureMiddleware::class)->post('/', function () use (&$called) {
$called = true;
});

$response = $this->post('/', ['example' => 'content']);

$this->assertFalse($called);
$response->assertStatus(400);
}

/** @test */
public function it_blocks_the_request_when_the_middleware_is_applied_without_a_secret_being_configured(): void
{
Config::set('plain.secret', '');

$called = false;
Route::middleware(VerifyPlainSignatureMiddleware::class)->post('/', function () use (&$called) {
$called = true;
});

$response = $this->post('/', ['example' => 'content'], [
'plain-request-signature' => 'example-signature',
]);

$this->assertFalse($called);
$response->assertStatus(403);
}

/** @test */
public function it_blocks_the_request_when_the_plain_signature_header_does_not_match_the_configured_secret(): void
{
Config::set('plain.secret', 'my-plain-secret');

$called = false;
Route::middleware(VerifyPlainSignatureMiddleware::class)->post('/', function () use (&$called) {
$called = true;
});

$response = $this->post('/', ['example' => 'content'], [
'plain-request-signature' => 'example-signature',
]);

$this->assertFalse($called);
$response->assertStatus(403);
}

/** @test */
public function it_allows_the_request_when_the_plain_signature_header_matches_the_configured_secret(): void
{
Config::set('plain.secret', 'my-plain-secret');

$called = false;
Route::middleware(VerifyPlainSignatureMiddleware::class)->post('/', function () use (&$called) {
$called = true;
});

$response = $this->post('/', ['example' => 'content'], [
'plain-request-signature' => 'e85ab1c2f80714be422adfc9f446f9c48a018c971df12527fba9b2a2819cd17c',
]);

$this->assertTrue($called);
$response->assertStatus(200);
}
}

0 comments on commit c51e03c

Please sign in to comment.