]> BookStack Code Mirror - bookstack/commitdiff
Exports: Added rate limits for UI exports 5379/head
authorDan Brown <redacted>
Wed, 1 Jan 2025 15:42:59 +0000 (15:42 +0000)
committerDan Brown <redacted>
Wed, 1 Jan 2025 15:42:59 +0000 (15:42 +0000)
Just as a measure to prevent potential abuse of these potentially
longer-running endpoints.
Adds test to cover for ZIP exports, but applied to all formats.

app/App/Providers/RouteServiceProvider.php
app/Exports/Controllers/BookExportController.php
app/Exports/Controllers/ChapterExportController.php
app/Exports/Controllers/PageExportController.php
tests/Exports/ZipExportTest.php

index d7c1cb737569618d5e8eb022499ef0e9cb27fe0a..97c3e7c770d0aa74fd2f173b33b9d21b93e5c46b 100644 (file)
@@ -85,5 +85,12 @@ class RouteServiceProvider extends ServiceProvider
         RateLimiter::for('public', function (Request $request) {
             return Limit::perMinute(10)->by($request->ip());
         });
+
+        RateLimiter::for('exports', function (Request $request) {
+            $user = user();
+            $attempts = $user->isGuest() ? 4 : 10;
+            $key = $user->isGuest() ? $request->ip() : $user->id;
+            return Limit::perMinute($attempts)->by($key);
+        });
     }
 }
index 184f7c2350e62d75df3dd384dde642c5c26b2b61..b6b1006bd61371b633cfb1a9c7f9f80a710345f2 100644 (file)
@@ -16,6 +16,7 @@ class BookExportController extends Controller
         protected ExportFormatter $exportFormatter,
     ) {
         $this->middleware('can:content-export');
+        $this->middleware('throttle:exports');
     }
 
     /**
index 4748ca6a80a4ce3d93aeea5068d5b055fc3538bc..de2385bb11fff1ba0fa9b0d30c3353702ebef541 100644 (file)
@@ -16,6 +16,7 @@ class ChapterExportController extends Controller
         protected ExportFormatter $exportFormatter,
     ) {
         $this->middleware('can:content-export');
+        $this->middleware('throttle:exports');
     }
 
     /**
index 203495fe900126b1a962a5f991a57980b875a786..d7145411eaad52c3c4ef8e5a5c5381864bb7e690 100644 (file)
@@ -17,6 +17,7 @@ class PageExportController extends Controller
         protected ExportFormatter $exportFormatter,
     ) {
         $this->middleware('can:content-export');
+        $this->middleware('throttle:exports');
     }
 
     /**
index 0e17bc0e01d5124092353beb75ceb0b983f5b90f..1434c013f7310116029318cb0d082492d935d7e7 100644 (file)
@@ -423,6 +423,28 @@ class ZipExportTest extends TestCase
         $this->assertStringContainsString("[Link to chapter]([[bsexport:chapter:{$chapter->id}]])", $pageData['markdown']);
     }
 
+    public function test_exports_rate_limited_low_for_guest_viewers()
+    {
+        $this->setSettings(['app-public' => 'true']);
+
+        $page = $this->entities->page();
+        for ($i = 0; $i < 4; $i++) {
+            $this->get($page->getUrl("/export/zip"))->assertOk();
+        }
+        $this->get($page->getUrl("/export/zip"))->assertStatus(429);
+    }
+
+    public function test_exports_rate_limited_higher_for_logged_in_viewers()
+    {
+        $this->asAdmin();
+
+        $page = $this->entities->page();
+        for ($i = 0; $i < 10; $i++) {
+            $this->get($page->getUrl("/export/zip"))->assertOk();
+        }
+        $this->get($page->getUrl("/export/zip"))->assertStatus(429);
+    }
+
     protected function extractZipResponse(TestResponse $response): ZipResultData
     {
         $zipData = $response->streamedContent();
Morty Proxy This is a proxified and sanitized view of the page, visit original site.