Skip to content

Commit a0d3ff6

Browse files
authored
Webhook Authenticator (#233)
1 parent a19aa64 commit a0d3ff6

12 files changed

Lines changed: 103 additions & 46 deletions

src/Auth/NullAuthenticator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
class NullAuthenticator implements WebhookAuthenticator
1010
{
11-
public function validate(Request $request): bool
11+
public function validate(Request $request): Request
1212
{
13-
return true;
13+
return $request;
1414
}
1515
}

src/Auth/SignatureAuthenticator.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88

99
class SignatureAuthenticator implements WebhookAuthenticator
1010
{
11-
public function validate(Request $request): bool
11+
public function validate(Request $request): Request
1212
{
13-
return hash_equals(
13+
if (! hash_equals(
1414
$request->header(config('workflows.webhook_auth.signature.header')) ?? '',
1515
hash_hmac('sha256', $request->getContent(), config('workflows.webhook_auth.signature.secret'))
16-
);
16+
)) {
17+
abort(401, 'Unauthorized');
18+
}
19+
return $request;
1720
}
1821
}

src/Auth/TokenAuthenticator.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88

99
class TokenAuthenticator implements WebhookAuthenticator
1010
{
11-
public function validate(Request $request): bool
11+
public function validate(Request $request): Request
1212
{
13-
return $request->header(config('workflows.webhook_auth.token.header')) === config(
13+
if ($request->header(config('workflows.webhook_auth.token.header')) !== config(
1414
'workflows.webhook_auth.token.token'
15-
);
15+
)) {
16+
abort(401, 'Unauthorized');
17+
}
18+
return $request;
1619
}
1720
}

src/Auth/WebhookAuthenticator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88

99
interface WebhookAuthenticator
1010
{
11-
public function validate(Request $request): bool;
11+
public function validate(Request $request): Request;
1212
}

src/Webhooks.php

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,7 @@ private static function registerWorkflowWebhooks($workflow, $basePath)
7979
if ($method->getName() === 'execute') {
8080
$slug = Str::kebab(class_basename($workflow));
8181
Route::post("{$basePath}/start/{$slug}", static function (Request $request) use ($workflow) {
82-
if (! self::validateAuth($request)) {
83-
return response()->json([
84-
'error' => 'Unauthorized',
85-
], 401);
86-
}
87-
82+
$request = self::validateAuth($request);
8883
$params = self::resolveNamedParameters($workflow, 'execute', $request->all());
8984
WorkflowStub::make($workflow)->start(...$params);
9085
return response()->json([
@@ -101,24 +96,17 @@ private static function registerSignalWebhooks($workflow, $basePath)
10196
if (self::hasWebhookAttributeOnMethod($method)) {
10297
$slug = Str::kebab(class_basename($workflow));
10398
$signal = Str::kebab($method->getName());
104-
10599
Route::post(
106100
"{$basePath}/signal/{$slug}/{workflowId}/{$signal}",
107101
static function (Request $request, $workflowId) use ($workflow, $method) {
108-
if (! self::validateAuth($request)) {
109-
return response()->json([
110-
'error' => 'Unauthorized',
111-
], 401);
112-
}
113-
102+
$request = self::validateAuth($request);
114103
$workflowInstance = WorkflowStub::load($workflowId);
115104
$params = self::resolveNamedParameters(
116105
$workflow,
117106
$method->getName(),
118107
$request->except('workflow_id')
119108
);
120109
$workflowInstance->{$method->getName()}(...$params);
121-
122110
return response()->json([
123111
'message' => 'Signal sent',
124112
]);
@@ -164,7 +152,7 @@ private static function resolveNamedParameters($class, $method, $payload)
164152
return $params;
165153
}
166154

167-
private static function validateAuth(Request $request): bool
155+
private static function validateAuth(Request $request): Request
168156
{
169157
$authenticatorClass = match (config('workflows.webhook_auth.method', 'none')) {
170158
'none' => NullAuthenticator::class,
@@ -174,10 +162,10 @@ private static function validateAuth(Request $request): bool
174162
default => null,
175163
};
176164

177-
if (is_subclass_of($authenticatorClass, WebhookAuthenticator::class)) {
178-
return (new $authenticatorClass())->validate($request);
165+
if (! is_subclass_of($authenticatorClass, WebhookAuthenticator::class)) {
166+
abort(401, 'Unauthorized');
179167
}
180168

181-
return false;
169+
return (new $authenticatorClass())->validate($request);
182170
}
183171
}

tests/Feature/ExceptionWorkflowTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
namespace Tests\Feature;
66

7-
use Tests\Fixtures\NonRetryableTestExceptionWorkflow;
87
use Tests\Fixtures\TestExceptionWorkflow;
8+
use Tests\Fixtures\TestNonRetryableExceptionWorkflow;
99
use Tests\TestCase;
1010
use Workflow\Serializers\Serializer;
1111
use Workflow\States\WorkflowCompletedStatus;
@@ -34,7 +34,7 @@ public function testRetry(): void
3434

3535
public function testNonRetryableException(): void
3636
{
37-
$workflow = WorkflowStub::make(NonRetryableTestExceptionWorkflow::class);
37+
$workflow = WorkflowStub::make(TestNonRetryableExceptionWorkflow::class);
3838

3939
$workflow->start();
4040

tests/Feature/WebhookWorkflowTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public function testSignatureAuth()
111111

112112
$response->assertStatus(401);
113113
$response->assertJson([
114-
'error' => 'Unauthorized',
114+
'message' => 'Unauthorized',
115115
]);
116116

117117
$response = $this->postJson('/webhooks/start/test-webhook-workflow', [], [
@@ -120,7 +120,7 @@ public function testSignatureAuth()
120120

121121
$response->assertStatus(401);
122122
$response->assertJson([
123-
'error' => 'Unauthorized',
123+
'message' => 'Unauthorized',
124124
]);
125125

126126
$response = $this->postJson('/webhooks/start/test-webhook-workflow', [], [
@@ -166,7 +166,7 @@ public function testTokenAuth()
166166

167167
$response->assertStatus(401);
168168
$response->assertJson([
169-
'error' => 'Unauthorized',
169+
'message' => 'Unauthorized',
170170
]);
171171

172172
$response = $this->postJson('/webhooks/start/test-webhook-workflow', [], [
@@ -175,7 +175,7 @@ public function testTokenAuth()
175175

176176
$response->assertStatus(401);
177177
$response->assertJson([
178-
'error' => 'Unauthorized',
178+
'message' => 'Unauthorized',
179179
]);
180180

181181
$response = $this->postJson('/webhooks/start/test-webhook-workflow', [], [
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Fixtures;
6+
7+
use Illuminate\Http\Request;
8+
use Workflow\Auth\WebhookAuthenticator;
9+
10+
class TestAuthenticator implements WebhookAuthenticator
11+
{
12+
public function validate(Request $request): Request
13+
{
14+
if ($request->header('Authorization')) {
15+
return $request;
16+
}
17+
abort(401, 'Unauthorized');
18+
}
19+
}

tests/Fixtures/NonRetryableTestExceptionActivity.php renamed to tests/Fixtures/TestNonRetryableExceptionActivity.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Workflow\Activity;
88
use Workflow\Exceptions\NonRetryableException;
99

10-
final class NonRetryableTestExceptionActivity extends Activity
10+
final class TestNonRetryableExceptionActivity extends Activity
1111
{
1212
public function execute()
1313
{

tests/Fixtures/NonRetryableTestExceptionWorkflow.php renamed to tests/Fixtures/TestNonRetryableExceptionWorkflow.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
use Workflow\ActivityStub;
88
use Workflow\Workflow;
99

10-
final class NonRetryableTestExceptionWorkflow extends Workflow
10+
final class TestNonRetryableExceptionWorkflow extends Workflow
1111
{
1212
public function execute()
1313
{
14-
yield ActivityStub::make(NonRetryableTestExceptionActivity::class);
14+
yield ActivityStub::make(TestNonRetryableExceptionActivity::class);
1515
yield ActivityStub::make(TestActivity::class);
1616

1717
return 'Workflow completes';

0 commit comments

Comments
 (0)