From e59a2a115fe6511ce825bb68e72072db7cabb020 Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Mon, 13 Jul 2026 12:38:40 +0100 Subject: [PATCH] Cap password length at 72 characters to prevent hashing DoS. Enforces the limit on registration, login, and password reset, with frontend maxlength and tests to ensure normal passwords still work. Co-authored-by: Cursor --- app/Http/Controllers/Auth/LoginController.php | 9 +++ .../Controllers/Auth/RegisterController.php | 2 +- .../Auth/ResetPasswordController.php | 13 ++++ app/Providers/AppServiceProvider.php | 1 + resources/js/components/PasswordField.vue | 1 + .../Auth/PasswordLengthValidationTest.php | 61 +++++++++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/Auth/PasswordLengthValidationTest.php diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index f76536fab..eb4ca7672 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; @@ -44,6 +45,14 @@ public function __construct() $this->middleware('guest')->except('logout'); } + protected function validateLogin(Request $request): void + { + $request->validate([ + $this->username() => 'required|string', + 'password' => 'required|string|max:72', + ]); + } + /** * Redirect the user to the GitHub authentication page. */ diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 2a6524b1b..2a8cfd678 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -50,7 +50,7 @@ protected function validator(array $data): \Illuminate\Contracts\Validation\Vali return Validator::make($data, [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', - 'password' => Password::defaults(), + 'password' => ['required', 'string', Password::defaults()], 'privacy' => 'required', ]); } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index cf726eecd..6ead274a5 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Illuminate\Validation\Rules\Password; class ResetPasswordController extends Controller { @@ -36,4 +37,16 @@ public function __construct() { $this->middleware('guest'); } + + /** + * @return array + */ + protected function rules(): array + { + return [ + 'token' => 'required', + 'email' => 'required|email', + 'password' => ['required', 'string', 'confirmed', Password::defaults()], + ]; + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 999317448..92cb98595 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -39,6 +39,7 @@ public function boot(): void ->mixedCase() ->numbers() ->symbols() + ->max(72) ->uncompromised(); }); diff --git a/resources/js/components/PasswordField.vue b/resources/js/components/PasswordField.vue index 552299e55..aabf23619 100644 --- a/resources/js/components/PasswordField.vue +++ b/resources/js/components/PasswordField.vue @@ -8,6 +8,7 @@ :type="type" :defaultValue="value" :required="required" + maxlength="72" />
post('/register', [ + 'name' => 'Test User', + 'email' => 'long-password@example.com', + 'password' => Str::repeat('Aa1!', 19), // 76 chars + 'password_confirmation' => Str::repeat('Aa1!', 19), + 'privacy' => '1', + ]); + + $response->assertSessionHasErrors('password'); + $this->assertDatabaseMissing('users', ['email' => 'long-password@example.com']); + } + + public function test_login_rejects_password_longer_than_72_characters(): void + { + $response = $this->post('/login', [ + 'email' => 'user@example.com', + 'password' => Str::repeat('x', 73), + ]); + + $response->assertSessionHasErrors('password'); + } + + public function test_registration_page_renders_password_fields(): void + { + $response = $this->get('/register'); + + $response->assertOk(); + $response->assertSee('name="password"', false); + $response->assertSee('name="password_confirmation"', false); + } + + public function test_registration_accepts_password_at_max_length(): void + { + $password = Str::repeat('Aa1!', 18); // 72 chars, meets complexity rules + $this->assertSame(72, strlen($password)); + + $response = $this->post('/register', [ + 'name' => 'Test User', + 'email' => 'max-length@example.com', + 'password' => $password, + 'password_confirmation' => $password, + 'privacy' => '1', + ]); + + $response->assertSessionDoesntHaveErrors(['password']); + } +}