Skip to content

Commit 4d18c1e

Browse files
jasonvargaclaude
andcommitted
rate limit elevated session endpoints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5291fc8 commit 4d18c1e

3 files changed

Lines changed: 41 additions & 4 deletions

File tree

routes/cp.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,8 @@
445445

446446
Route::get('auth/confirm-password', [ElevatedSessionController::class, 'showForm'])->name('confirm-password');
447447
Route::get('elevated-session', [ElevatedSessionController::class, 'status'])->name('elevated-session.status');
448-
Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options');
449-
Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm');
448+
Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options')->middleware('throttle:statamic.cp.passkeys');
449+
Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm')->middleware('throttle:statamic.cp.auth');
450450
Route::get('elevated-session/resend-code', [ElevatedSessionController::class, 'resendCode'])->name('elevated-session.resend-code')->middleware('throttle:send-elevated-session-code');
451451

452452
Route::get('playground', PlaygroundController::class)->name('playground');

routes/web.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@
5656

5757
Route::middleware('auth')->group(function () {
5858
Route::get('confirm-password', [ElevatedSessionController::class, 'showForm'])->name('elevated-session')->middleware([HandleInertiaRequests::class]);
59-
Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm');
60-
Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options');
59+
Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm')->middleware('throttle:statamic.auth');
60+
Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options')->middleware('throttle:statamic.passkeys');
6161
Route::get('elevated-session/resend-code', [ElevatedSessionController::class, 'resendCode'])->name('elevated-session.resend-code')->middleware('throttle:send-elevated-session-code');
6262
});
6363

tests/Feature/RateLimitingTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Support\Facades\Cache;
77
use Illuminate\Support\Facades\RateLimiter;
88
use PHPUnit\Framework\Attributes\Test;
9+
use Statamic\Facades\User;
910
use Tests\PreventSavingStacheItemsToDisk;
1011
use Tests\TestCase;
1112

@@ -143,6 +144,42 @@ public function cp_passkey_endpoint_is_rate_limited()
143144
$this->post('/cp/auth/passkeys')->assertRateLimited();
144145
}
145146

147+
#[Test]
148+
public function elevated_session_confirm_endpoint_is_rate_limited()
149+
{
150+
$this->actingAs(tap(User::make()->email('foo@bar.com'))->save());
151+
152+
collect(range(1, 4))->each(fn () => $this->post('/!/auth/elevated-session')->assertNotRateLimited());
153+
$this->post('/!/auth/elevated-session')->assertRateLimited();
154+
}
155+
156+
#[Test]
157+
public function cp_elevated_session_confirm_endpoint_is_rate_limited()
158+
{
159+
$this->actingAs(tap(User::make()->email('foo@bar.com')->makeSuper())->save());
160+
161+
collect(range(1, 4))->each(fn () => $this->post('/cp/elevated-session')->assertNotRateLimited());
162+
$this->post('/cp/elevated-session')->assertRateLimited();
163+
}
164+
165+
#[Test]
166+
public function elevated_session_passkey_options_endpoint_is_rate_limited()
167+
{
168+
$this->actingAs(tap(User::make()->email('foo@bar.com'))->save());
169+
170+
collect(range(1, 30))->each(fn () => $this->get('/!/auth/elevated-session/passkey-options')->assertNotRateLimited());
171+
$this->get('/!/auth/elevated-session/passkey-options')->assertRateLimited();
172+
}
173+
174+
#[Test]
175+
public function cp_elevated_session_passkey_options_endpoint_is_rate_limited()
176+
{
177+
$this->actingAs(tap(User::make()->email('foo@bar.com')->makeSuper())->save());
178+
179+
collect(range(1, 30))->each(fn () => $this->get('/cp/elevated-session/passkey-options')->assertNotRateLimited());
180+
$this->get('/cp/elevated-session/passkey-options')->assertRateLimited();
181+
}
182+
146183
#[Test]
147184
public function cp_and_frontend_passkeys_have_independent_buckets()
148185
{

0 commit comments

Comments
 (0)