diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 565c2e60..abe7d785 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,18 +12,9 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3, 8.4, 8.5] - laravel: [10, 11, 12] + php: [8.2, 8.3, 8.4, 8.5] + laravel: [11, 12] stability: ["prefer-lowest", "prefer-stable"] - exclude: - - php: 8.5 - laravel: 10 - - php: 8.4 - laravel: 10 - - php: 8.1 - laravel: 11 - - php: 8.1 - laravel: 12 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} (w/ ${{ matrix.stability }}) steps: @@ -39,10 +30,6 @@ jobs: tools: composer:v2 coverage: none - - name: Remove PHPStan on PHP 8.1 and Laravel 10 - run: composer remove --dev larastan/larastan - if: matrix.php == 8.1 || matrix.laravel == 10 - - name: Set Minimum PHP 8.1 Versions uses: nick-fields/retry@v3 with: diff --git a/composer.json b/composer.json index 485a188f..98fa1d3d 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require": { "php": "^8.1.0", "ext-json": "*", - "laravel/framework": "^10.0|^11.0|^12.0", + "laravel/framework": "^11.27|^12.0", "symfony/console": "^6.2|^7.0" }, "require-dev": { diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 920b602a..b6b75e4b 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,8 +2,10 @@ namespace Inertia; +use Illuminate\Foundation\Http\Kernel; use Illuminate\Http\Request; use Illuminate\Routing\Router; +use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\ServiceProvider as BaseServiceProvider; use Illuminate\Testing\TestResponse; use Illuminate\View\FileViewFinder; @@ -57,12 +59,24 @@ public function register(): void public function boot(): void { $this->registerConsoleCommands(); + $this->configureMiddlewarePriority(); $this->publishes([ __DIR__.'/../config/inertia.php' => config_path('inertia.php'), ]); } + /** + * Configure middleware priority to ensure Inertia can intercept + * redirect responses from other middleware (like throttle). + */ + protected function configureMiddlewarePriority(): void + { + $this->app->afterResolving(Kernel::class, function (Kernel $kernel) { + $kernel->addToMiddlewarePriorityAfter(StartSession::class, Middleware::class); + }); + } + /** * Register @inertia and @inertiaHead directives for rendering the Inertia * root element and SSR head content in Blade templates. diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index efb270b4..d412eff2 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -2,9 +2,16 @@ namespace Inertia\Tests; +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Foundation\Http\Kernel; use Illuminate\Http\Request; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Routing\Router; +use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\Facades\Blade; +use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\Route; +use Inertia\Tests\Stubs\ExampleMiddleware; class ServiceProviderTest extends TestCase { @@ -39,4 +46,55 @@ public function test_route_macro_is_registered(): void $this->assertEquals(['uses' => '\Inertia\Controller@__invoke', 'controller' => '\Inertia\Controller'], $inertiaRoute->action); $this->assertEquals(['component' => 'User/Edit', 'props' => ['user' => ['name' => 'Jonathan']]], $inertiaRoute->defaults); } + + public function test_inertia_middleware_is_prioritized_after_start_session(): void + { + $route = Route::middleware(['web', ExampleMiddleware::class, 'throttle:api']) + ->delete('/foo', fn () => 'ok'); + + // Resolve Kernel to register middleware groups + app(Kernel::class); + + $middleware = app(Router::class)->resolveMiddleware($route->gatherMiddleware()); + + $startSessionIndex = array_search(StartSession::class, $middleware); + $inertiaIndex = array_search(ExampleMiddleware::class, $middleware); + $throttleIndex = array_search(ThrottleRequests::class.':api', $middleware); + + $this->assertNotFalse($startSessionIndex); + $this->assertNotFalse($inertiaIndex); + $this->assertNotFalse($throttleIndex); + + $this->assertTrue( + $startSessionIndex < $inertiaIndex, + 'StartSession middleware must run before Inertia middleware.' + ); + + $this->assertTrue( + $inertiaIndex < $throttleIndex, + 'Inertia middleware must run before ThrottleRequests middleware.' + ); + } + + public function test_redirect_response_from_rate_limiter_is_converted_to_303(): void + { + RateLimiter::for('api', fn () => Limit::perMinute(1)->response(fn () => back())); + + // Needed for the web middleware + config(['app.key' => 'base64:'.base64_encode(random_bytes(32))]); + + Route::middleware(['web', ExampleMiddleware::class, 'throttle:api']) + ->delete('/foo', fn () => 'ok'); + + $this + ->from('/bar') + ->delete('/foo', [], ['X-Inertia' => 'true']) + ->assertOk(); + + $this + ->from('/bar') + ->delete('/foo', [], ['X-Inertia' => 'true']) + ->assertRedirect('/bar') + ->assertStatus(303); + } }