diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d2e0f2cc..30fcd8ce 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -112,7 +112,6 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // API Specific Middleware 'api..key' => AuthenticateKey::class, ]; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 0ea33b5d..2dedacb4 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,7 +2,10 @@ namespace Pterodactyl\Providers; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Support\Facades\RateLimiter; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider @@ -17,43 +20,86 @@ class RouteServiceProvider extends ServiceProvider protected $namespace = 'Pterodactyl\Http\Controllers'; /** - * Define the routes for the application. + * Define your route model bindings, pattern filters, etc. */ - public function map() + public function boot() { - Route::middleware(['web', 'auth', 'csrf']) - ->namespace($this->namespace . '\Base') - ->group(base_path('routes/base.php')); + $this->configureRateLimiting(); - Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') - ->namespace($this->namespace . '\Admin') - ->group(base_path('routes/admin.php')); + $this->routes(function () { + Route::middleware(['web', 'auth', 'csrf']) + ->namespace("$this->namespace\\Base") + ->group(base_path('routes/base.php')); - Route::middleware(['web', 'csrf'])->prefix('/auth') - ->namespace($this->namespace . '\Auth') - ->group(base_path('routes/auth.php')); + Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') + ->namespace("$this->namespace\\Admin") + ->group(base_path('routes/admin.php')); - Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) - ->prefix('/api/server/{server}') - ->namespace($this->namespace . '\Server') - ->group(base_path('routes/server.php')); + Route::middleware(['web', 'csrf'])->prefix('/auth') + ->namespace("$this->namespace\\Auth") + ->group(base_path('routes/auth.php')); - Route::middleware([ - sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')), - 'api', - ])->prefix('/api/application') - ->namespace($this->namespace . '\Api\Application') - ->group(base_path('routes/api-application.php')); + Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) + ->prefix('/api/server/{server}') + ->namespace("$this->namespace\\Server") + ->group(base_path('routes/server.php')); - Route::middleware([ - sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), - 'client-api', - ])->prefix('/api/client') - ->namespace($this->namespace . '\Api\Client') - ->group(base_path('routes/api-client.php')); + Route::middleware(['api', 'throttle:api.application']) + ->prefix('/api/application') + ->namespace("$this->namespace\\Api\\Application") + ->group(base_path('routes/api-application.php')); - Route::middleware(['daemon'])->prefix('/api/remote') - ->namespace($this->namespace . '\Api\Remote') - ->group(base_path('routes/api-remote.php')); + Route::middleware(['client-api', 'throttle:api.client']) + ->prefix('/api/client') + ->namespace("$this->namespace\\Api\\Client") + ->group(base_path('routes/api-client.php')); + + Route::middleware(['daemon'])->prefix('/api/remote') + ->namespace("$this->namespace\\Api\\Remote") + ->group(base_path('routes/api-remote.php')); + }); + } + + /** + * Configure the rate limiters for the application. + */ + protected function configureRateLimiting() + { + // Authentication rate limiting. For login and checkpoint endpoints we'll apply + // a limit of 10 requests per minute, for the forgot password endpoint apply a + // limit of two per minute for the requester so that there is less ability to + // trigger email spam. + RateLimiter::for('authentication', function (Request $request) { + if ($request->route()->named('auth.post.forgot-password')) { + return Limit::perMinute(2)->by($request->ip()); + } + + return Limit::perMinute(10); + }); + + // Configure the throttles for both the application and client APIs below. + // This is configurable per-instance in "config/http.php". By default this + // limiter will be tied to the specific request user, and falls back to the + // request IP if there is no request user present for the key. + // + // This means that an authenticated API user cannot use IP switching to get + // around the limits. + RateLimiter::for('api.client', function (Request $request) { + $key = optional($request->user())->uuid ?: $request->ip(); + + return Limit::perMinutes( + config('http.rate_limit.client_period'), + config('http.rate_limit.client') + )->by($key); + }); + + RateLimiter::for('api.application', function (Request $request) { + $key = optional($request->user())->uuid ?: $request->ip(); + + return Limit::perMinutes( + config('http.rate_limit.application_period'), + config('http.rate_limit.application') + )->by($key); + }); } } diff --git a/routes/auth.php b/routes/auth.php index 4bdb7220..2e9a01ea 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -15,13 +15,21 @@ Route::group(['middleware' => 'guest'], function () { Route::get('/password', 'LoginController@index')->name('auth.forgot-password'); Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset'); - // Login endpoints. - Route::post('/login', 'LoginController@login')->middleware('recaptcha'); - Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); + // Apply a throttle to authentication action endpoints, in addition to the + // recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ + // + // @see \Pterodactyl\Providers\RouteServiceProvider + Route::middleware(['throttle:authentication'])->group(function () { + // Login endpoints. + Route::post('/login', 'LoginController@login')->middleware('recaptcha'); + Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); - // Forgot password route. A post to this endpoint will trigger an - // email to be sent containing a reset token. - Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); + // Forgot password route. A post to this endpoint will trigger an + // email to be sent containing a reset token. + Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail') + ->name('auth.post.forgot-password') + ->middleware('recaptcha'); + }); // Password reset routes. This endpoint is hit after going through // the forgot password routes to acquire a token (or after an account