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
30 changes: 30 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,34 @@

'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:5,1' to allow 5 attempts per minute.
|
*/

'auth_middleware' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':5,1',
],

/*
|--------------------------------------------------------------------------
| Forms Route Middleware
|--------------------------------------------------------------------------
|
| Additional middleware applied to the frontend form submission route.
| Useful for rate limiting, e.g. 'throttle:10,1' to allow 10 submissions
| per minute.
|
*/

'forms_middleware' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':10,1',
],

];
4 changes: 2 additions & 2 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
124 changes: 124 additions & 0 deletions tests/Routing/RouteMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Tests\Routing;

use Illuminate\Routing\Middleware\ThrottleRequests;
use Orchestra\Testbench\Attributes\DefineEnvironment;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Blueprint;
use Statamic\Facades\Form;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

class RouteMiddlewareTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

protected function withAuthThrottleMiddleware($app)
{
$app['config']->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();
}
}
Loading