From fe4629e7e6e7925bcd040a90466a97c0a371b86e Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 10 Apr 2026 11:37:22 +0100 Subject: [PATCH 1/3] Allow configs to be added to frontend form and auth end routes --- config/routes.php | 26 ++++++++++++++++++++++++++ routes/web.php | 4 ++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/config/routes.php b/config/routes.php index 95a6427684c..c390a215b50 100644 --- a/config/routes.php +++ b/config/routes.php @@ -53,4 +53,30 @@ 'middleware' => 'web', + /* + |-------------------------------------------------------------------------- + | Auth Route Middleware + |-------------------------------------------------------------------------- + | + | Additional middleware applied to the frontend auth routes (login, + | register, password reset, etc). Useful for rate limiting, e.g. + | 'throttle:4,1' to allow 4 attempts per minute. + | + */ + + 'auth_middleware' => [], + + /* + |-------------------------------------------------------------------------- + | Forms Route Middleware + |-------------------------------------------------------------------------- + | + | Additional middleware applied to the frontend form submission route. + | Useful for rate limiting, e.g. 'throttle:30,1' to allow 30 submissions + | per minute. + | + */ + + 'forms_middleware' => [], + ]; diff --git a/routes/web.php b/routes/web.php index a1f706681a1..aff7df3b899 100755 --- a/routes/web.php +++ b/routes/web.php @@ -34,12 +34,12 @@ Route::name('statamic.')->group(function () { Route::group(['prefix' => config('statamic.routes.action')], function () { - Route::post('forms/{form}', [FormController::class, 'submit'])->middleware([HandlePrecognitiveRequests::class])->name('forms.submit'); + Route::post('forms/{form}', [FormController::class, 'submit'])->middleware(array_merge([HandlePrecognitiveRequests::class], (array) config('statamic.routes.forms_middleware', [])))->name('forms.submit'); Route::get('protect/password', [PasswordProtectController::class, 'show'])->name('protect.password.show')->middleware([HandleInertiaRequests::class]); Route::post('protect/password', [PasswordProtectController::class, 'store'])->name('protect.password.store'); - Route::group(['prefix' => 'auth', 'middleware' => [AuthGuard::class]], function () { + Route::group(['prefix' => 'auth', 'middleware' => array_merge([AuthGuard::class], (array) config('statamic.routes.auth_middleware', []))], function () { Route::get('logout', [LoginController::class, 'logout'])->name('logout'); Route::group(['middleware' => [HandlePrecognitiveRequests::class]], function () { From a54da1e2cd81e7eeb3c05ed603d77b8e36925549 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 10 Apr 2026 11:40:27 +0100 Subject: [PATCH 2/3] test coverage --- tests/Routing/RouteMiddlewareTest.php | 124 ++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/Routing/RouteMiddlewareTest.php diff --git a/tests/Routing/RouteMiddlewareTest.php b/tests/Routing/RouteMiddlewareTest.php new file mode 100644 index 00000000000..5f95af80d63 --- /dev/null +++ b/tests/Routing/RouteMiddlewareTest.php @@ -0,0 +1,124 @@ +set('statamic.routes.auth_middleware', [ThrottleRequests::class.':2,1']); + } + + protected function withFormsThrottleMiddleware($app) + { + $app['config']->set('statamic.routes.forms_middleware', [ThrottleRequests::class.':2,1']); + } + + #[Test] + public function no_extra_middleware_is_applied_to_auth_routes_by_default() + { + for ($i = 0; $i < 5; $i++) { + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong']) + ->assertStatus(302); + } + } + + #[Test] + #[DefineEnvironment('withAuthThrottleMiddleware')] + public function custom_middleware_is_applied_to_auth_login_route() + { + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(429); + } + + #[Test] + #[DefineEnvironment('withAuthThrottleMiddleware')] + public function custom_auth_middleware_is_applied_to_all_auth_routes() + { + $this->post('/!/auth/password/email', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/auth/password/email', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/auth/password/email', ['email' => 'test@example.com'])->assertStatus(429); + } + + #[Test] + #[DefineEnvironment('withAuthThrottleMiddleware')] + public function custom_auth_middleware_does_not_affect_forms_route() + { + $this->createContactForm(); + + // Auth routes reach the throttle limit + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(429); + + // Forms route is unaffected + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + } + + #[Test] + public function no_extra_middleware_is_applied_to_forms_route_by_default() + { + $this->createContactForm(); + + for ($i = 0; $i < 5; $i++) { + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + } + } + + #[Test] + #[DefineEnvironment('withFormsThrottleMiddleware')] + public function custom_middleware_is_applied_to_forms_route() + { + $this->createContactForm(); + + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(429); + } + + #[Test] + #[DefineEnvironment('withFormsThrottleMiddleware')] + public function custom_forms_middleware_does_not_affect_auth_routes() + { + $this->createContactForm(); + + // Forms route reaches the throttle limit + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(302); + $this->post('/!/forms/contact', ['email' => 'test@example.com'])->assertStatus(429); + + // Auth routes are unaffected + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + $this->post('/!/auth/login', ['email' => 'test@example.com', 'password' => 'wrong'])->assertStatus(302); + } + + private function createContactForm(): void + { + $blueprint = Blueprint::make()->setContents([ + 'fields' => [ + ['handle' => 'email', 'field' => ['type' => 'text', 'validate' => 'required|email']], + ], + ]); + + Blueprint::shouldReceive('find')->with('forms.contact')->andReturn($blueprint); + Blueprint::makePartial(); + + $form = Form::make()->handle('contact'); + Form::shouldReceive('find')->with('contact')->andReturn($form); + Form::makePartial(); + } +} From 784b1754a0955798d04de0129a0958f09ab72171 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 10 Apr 2026 12:13:11 +0100 Subject: [PATCH 3/3] add some sensible defaults --- config/routes.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/routes.php b/config/routes.php index c390a215b50..b9de16a738b 100644 --- a/config/routes.php +++ b/config/routes.php @@ -60,11 +60,13 @@ | | Additional middleware applied to the frontend auth routes (login, | register, password reset, etc). Useful for rate limiting, e.g. - | 'throttle:4,1' to allow 4 attempts per minute. + | 'throttle:5,1' to allow 5 attempts per minute. | */ - 'auth_middleware' => [], + 'auth_middleware' => [ + \Illuminate\Routing\Middleware\ThrottleRequests::class.':5,1', + ], /* |-------------------------------------------------------------------------- @@ -72,11 +74,13 @@ |-------------------------------------------------------------------------- | | Additional middleware applied to the frontend form submission route. - | Useful for rate limiting, e.g. 'throttle:30,1' to allow 30 submissions + | Useful for rate limiting, e.g. 'throttle:10,1' to allow 10 submissions | per minute. | */ - 'forms_middleware' => [], + 'forms_middleware' => [ + \Illuminate\Routing\Middleware\ThrottleRequests::class.':10,1', + ], ];