diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 52848a7ac..3eb360b1a 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -37,7 +37,7 @@ public function store(Request $request): RedirectResponse $request->validate([ 'name' => ['required', 'string', 'max:255'], 'username' => ['required', 'string', 'min:4', 'max:50', 'unique:'.User::class, new Username], - 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class, new UnauthorizedEmailProviders()], + 'email' => ['required', 'string', 'lowercase', 'email:rfc,dns', 'max:255', 'unique:'.User::class, new UnauthorizedEmailProviders()], 'password' => ['required', 'confirmed', Rules\Password::defaults()], 'terms' => ['required', 'accepted'], 'g-recaptcha-response' => app()->environment('production') ? ['required', new Recaptcha($request->ip())] : [], diff --git a/app/Http/Requests/UserUpdateRequest.php b/app/Http/Requests/UserUpdateRequest.php index efb795f96..6e7fa6b35 100644 --- a/app/Http/Requests/UserUpdateRequest.php +++ b/app/Http/Requests/UserUpdateRequest.php @@ -33,7 +33,7 @@ public function rules(): array new Username($user), ], 'email' => [ - 'required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($user->id), + 'required', 'string', 'lowercase', 'email:rfc,dns', 'max:255', Rule::unique(User::class)->ignore($user->id), new UnauthorizedEmailProviders(), ], 'mail_preference_time' => [Rule::enum(UserMailPreference::class)], diff --git a/composer.json b/composer.json index cd69982f5..b0dad8d03 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,7 @@ "type": "project", "require": { "php": "^8.3", + "ext-intl": "*", "filament/filament": "^3.2.115", "intervention/image": "^3.8.0", "laravel/fortify": "^1.21.1", diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index fdd944edf..1730e9826 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -42,7 +42,7 @@ public function definition(): array return [ 'name' => fake()->name(), 'username' => fake()->unique()->userName(), - 'email' => fake()->unique()->safeEmail(), + 'email' => fake()->unique()->freeEmail(), 'email_verified_at' => now(), 'password' => self::$password ??= Hash::make('password'), 'remember_token' => Str::random(10), diff --git a/tests/Http/Profile/EditTest.php b/tests/Http/Profile/EditTest.php index 605777452..6a2766c09 100644 --- a/tests/Http/Profile/EditTest.php +++ b/tests/Http/Profile/EditTest.php @@ -31,13 +31,14 @@ test('profile information can be updated', function () { $user = User::factory()->create(); + $freeEmail = fake()->unique()->freeEmail(); $response = $this ->actingAs($user) ->patch('/profile', [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => $freeEmail, 'mail_preference_time' => 'daily', 'prefers_anonymous_questions' => false, ]); @@ -49,7 +50,7 @@ $user->refresh(); $this->assertSame('Test User', $user->name); - $this->assertSame('test@example.com', $user->email); + $this->assertSame($freeEmail, $user->email); $this->assertSame('testuser', $user->username); $this->assertNull($user->email_verified_at); $this->assertFalse($user->prefers_anonymous_questions); @@ -153,7 +154,7 @@ ->patch('/profile', [ 'name' => $user->name, 'username' => 'valid_username', - 'email' => 'new@email.address', + 'email' => fake()->unique()->freeEmail(), 'prefers_anonymous_questions' => false, ]) ->assertSessionHasNoErrors(); @@ -462,3 +463,19 @@ ->and($user->is_uploaded_avatar)->toBeFalse() ->and(session('flash-message'))->toBe('Updating avatar using GitHub.'); }); + +test('reject profile information update with fake email', function () { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->patch('/profile', [ + 'name' => 'Test User', + 'username' => 'testuser', + 'email' => 'test@example.com', + 'mail_preference_time' => 'daily', + 'prefers_anonymous_questions' => false, + ]); + + $response->assertInvalid('email'); +}); diff --git a/tests/Http/Register/CreateTest.php b/tests/Http/Register/CreateTest.php index efe07e6c6..e98f80806 100644 --- a/tests/Http/Register/CreateTest.php +++ b/tests/Http/Register/CreateTest.php @@ -22,7 +22,7 @@ $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'm@9v_.*.XCN', 'password_confirmation' => 'm@9v_.*.XCN', 'terms' => true, @@ -40,7 +40,7 @@ $payload = [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'password', 'password_confirmation' => 'password', ]; @@ -85,7 +85,7 @@ $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'password', 'password_confirmation' => 'not-password', ]); @@ -98,7 +98,7 @@ $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'password', 'password_confirmation' => 'not-password', 'terms' => false, @@ -116,7 +116,7 @@ $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'password', 'password_confirmation' => 'password', ]); @@ -126,14 +126,15 @@ }); test('email must be unique', function () { + $freeEmail = fake()->unique()->freeEmail(); User::factory()->create([ - 'email' => 'test@example.com', + 'email' => $freeEmail, ]); $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser1', - 'email' => 'test@example.com', + 'email' => $freeEmail, 'password' => 'password', 'password_confirmation' => 'password', ]); @@ -148,7 +149,7 @@ $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'pass', 'password_confirmation' => 'pass', ]); @@ -224,7 +225,7 @@ $response = $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => $username, - 'email' => 'test@laravel.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'password', 'password_confirmation' => 'password', ]); @@ -277,16 +278,17 @@ ]); test("user's name can contain blank characters", function (string $given, string $expected) { + $freeEmail = fake()->unique()->freeEmail(); $response = $this->from('/register')->post('/register', [ 'name' => $given, 'username' => 'testuser', - 'email' => 'test@laravel.com', + 'email' => $freeEmail, 'password' => 'password', 'password_confirmation' => 'password', 'terms' => true, ]); - $user = User::where('email', 'test@laravel.com')->first(); + $user = User::where('email', $freeEmail)->first(); expect($user->name)->toBe($expected); })->with([ @@ -299,7 +301,7 @@ $this->from('/register')->post('/register', [ 'name' => 'Test User', 'username' => 'testuser1', - 'email' => 'test@example.com', + 'email' => fake()->unique()->freeEmail(), 'password' => 'password', 'password_confirmation' => 'password', 'terms' => true, @@ -307,3 +309,23 @@ expect(User::first()->prefers_anonymous_questions)->toBeTrue(); }); + +test('reject fake emails', function () { + Http::fake([ + 'https://www.google.com/recaptcha/api/siteverify' => Http::response([ + 'success' => true, + ]), + ]); + + $response = $this->from('/register')->post('/register', [ + 'name' => 'Test User', + 'username' => 'testuser', + 'email' => 'test@example.com', + 'password' => 'm@9v_.*.XCN', + 'password_confirmation' => 'm@9v_.*.XCN', + 'terms' => true, + 'g-recaptcha-response' => 'valid', + ]); + + $response->assertInvalid('email'); +});