Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 38 additions & 76 deletions app/Http/Controllers/WidgetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,8 @@ public function config(Request $request): JsonResponse
return response()->json(['enabled' => false], 200);
}

// Validate origin domain if restrictions are set
if (!empty($settings->allowed_domains)) {
$origin = $request->header('Origin') ?? $request->header('Referer');

if ($origin) {
$domain = parse_url($origin, PHP_URL_HOST);
$allowed = false;

foreach ($settings->allowed_domains as $allowedDomain) {
if ($domain === $allowedDomain || str_ends_with($domain, '.' . $allowedDomain)) {
$allowed = true;
break;
}
}

if (!$allowed) {
return response()->json(['enabled' => false], 200);
}
}
if (! $this->isOriginAllowed($request, $settings->allowed_domains)) {
return response()->json(['enabled' => false], 200);
}

return response()->json([
Expand All @@ -69,25 +52,8 @@ public function submit(Request $request): JsonResponse
return response()->json(['error' => 'Widget is not enabled'], 403);
}

// Validate origin domain if restrictions are set
if (!empty($settings->allowed_domains)) {
$origin = $request->header('Origin') ?? $request->header('Referer');

if ($origin) {
$domain = parse_url($origin, PHP_URL_HOST);
$allowed = false;

foreach ($settings->allowed_domains as $allowedDomain) {
if ($domain === $allowedDomain || str_ends_with($domain, '.' . $allowedDomain)) {
$allowed = true;
break;
}
}

if (!$allowed) {
return response()->json(['error' => 'Domain not allowed'], 403);
}
}
if (! $this->isOriginAllowed($request, $settings->allowed_domains)) {
return response()->json(['error' => 'Domain not allowed'], 403);
}

// Validate request
Expand Down Expand Up @@ -155,25 +121,8 @@ public function activityConfig(Request $request): JsonResponse
return response()->json(['enabled' => false], 200);
}

// Validate origin domain if restrictions are set
if (!empty($settings->allowed_domains)) {
$origin = $request->header('Origin') ?? $request->header('Referer');

if ($origin) {
$domain = parse_url($origin, PHP_URL_HOST);
$allowed = false;

foreach ($settings->allowed_domains as $allowedDomain) {
if ($domain === $allowedDomain || str_ends_with($domain, '.' . $allowedDomain)) {
$allowed = true;
break;
}
}

if (!$allowed) {
return response()->json(['enabled' => false], 200);
}
}
if (! $this->isOriginAllowed($request, $settings->allowed_domains)) {
return response()->json(['enabled' => false], 200);
}

return response()->json([
Expand All @@ -196,25 +145,8 @@ public function activityList(Request $request): JsonResponse
return response()->json(['error' => 'Widget is not enabled'], 403);
}

// Validate origin domain if restrictions are set
if (!empty($settings->allowed_domains)) {
$origin = $request->header('Origin') ?? $request->header('Referer');

if ($origin) {
$domain = parse_url($origin, PHP_URL_HOST);
$allowed = false;

foreach ($settings->allowed_domains as $allowedDomain) {
if ($domain === $allowedDomain || str_ends_with($domain, '.' . $allowedDomain)) {
$allowed = true;
break;
}
}

if (!$allowed) {
return response()->json(['error' => 'Domain not allowed'], 403);
}
}
if (! $this->isOriginAllowed($request, $settings->allowed_domains)) {
return response()->json(['error' => 'Domain not allowed'], 403);
}

$page = max(1, (int) $request->input('page', 1));
Expand Down Expand Up @@ -281,4 +213,34 @@ public function activityList(Request $request): JsonResponse
'has_more' => $paginator->hasMorePages(),
]);
}

/**
* @param array<int, string> $allowedDomains
*/
private function isOriginAllowed(Request $request, array $allowedDomains): bool
{
if (empty($allowedDomains)) {
return true;
}

$origin = $request->header('Origin') ?? $request->header('Referer');

if (! is_string($origin)) {
return false;
}

$domain = parse_url($origin, PHP_URL_HOST);

if (! is_string($domain)) {
return false;
}

foreach ($allowedDomains as $allowedDomain) {
if ($domain === $allowedDomain || str_ends_with($domain, '.'.$allowedDomain)) {
return true;
}
}

return false;
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Widget/WidgetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use App\Models\Item;
use App\Settings\WidgetSettings;
use App\Settings\ActivityWidgetSettings;
use function Pest\Laravel\assertDatabaseHas;

beforeEach(function () {
Expand Down Expand Up @@ -120,6 +121,16 @@
$response->assertForbidden();
});

test('widget submission requires an origin when domains are restricted', function () {
$this->settings->allowed_domains = ['example.com'];
$this->settings->save();

$this->postJson('/api/widget/submit', [
'title' => 'Test Feedback',
'content' => 'This is a test feedback',
])->assertForbidden();
});

test('widget submission allows configured domains', function () {
$this->settings->allowed_domains = ['example.com'];
$this->settings->save();
Expand Down Expand Up @@ -158,6 +169,29 @@
]);
});

test('widget config is disabled without an origin when domains are restricted', function () {
$this->settings->allowed_domains = ['example.com'];
$this->settings->save();

$this->getJson('/api/widget/config')
->assertSuccessful()
->assertJson(['enabled' => false]);
});

test('activity widget requires an origin when domains are restricted', function () {
$settings = app(ActivityWidgetSettings::class);
$settings->enabled = true;
$settings->allowed_domains = ['example.com'];
$settings->save();

$this->getJson('/api/activity-widget/config')
->assertSuccessful()
->assertJson(['enabled' => false]);

$this->getJson('/api/activity-widget/activities')
->assertForbidden();
});

test('widget javascript is served correctly', function () {
$response = $this->get('/widget.js');

Expand Down
Loading