From 4f8bf46febd83c99296dcf816767421c7812ea6f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:10:18 +0200 Subject: [PATCH] fix(auth): remove the unused Fortify profile route that bypassed the SSO email lock PUT /user/profile-information changed the email with no SSO check and no verification. The profile page has its own verified email change, so the Fortify feature, its action and its binding are removed. --- .../Fortify/UpdateUserProfileInformation.php | 73 ------------------- app/Providers/FortifyServiceProvider.php | 2 - config/fortify.php | 1 - .../FortifyProfileInformationRouteTest.php | 38 ++++++++++ 4 files changed, 38 insertions(+), 76 deletions(-) delete mode 100644 app/Actions/Fortify/UpdateUserProfileInformation.php create mode 100644 tests/Feature/FortifyProfileInformationRouteTest.php diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php deleted file mode 100644 index dd3ff35b6f..0000000000 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ /dev/null @@ -1,73 +0,0 @@ - $input - */ - public function update(User $user, array $input): void - { - $changedFields = collect(['name', 'email']) - ->filter(fn (string $field): bool => $user->{$field} !== $input[$field]) - ->values() - ->all(); - Validator::make($input, [ - 'name' => ['required', 'string', 'max:255'], - - 'email' => [ - 'required', - 'string', - 'email', - 'max:255', - Rule::unique('users')->ignore($user->id), - ], - ])->validateWithBag('updateProfileInformation'); - - if ( - $input['email'] !== $user->email && - $user instanceof MustVerifyEmail - ) { - $this->updateVerifiedUser($user, $input); - } else { - $user->fill([ - 'name' => $input['name'], - 'email' => $input['email'], - ])->save(); - } - - if ($changedFields !== []) { - auditLog('ui.user.profile_updated', [ - 'team_id' => $user->currentTeam()?->id, - 'resource' => 'user', - 'user_name' => $user->name, - 'changed_fields' => $changedFields, - ]); - } - } - - /** - * Update the given verified user's profile information. - * - * @param array $input - */ - protected function updateVerifiedUser(User $user, array $input): void - { - $user->fill([ - 'name' => $input['name'], - 'email' => $input['email'], - 'email_verified_at' => null, - ])->save(); - - $user->sendEmailVerificationNotification(); - } -} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 50df845f7b..a340c2033b 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -5,7 +5,6 @@ namespace App\Providers; use App\Actions\Fortify\CreateNewUser; use App\Actions\Fortify\ResetUserPassword; use App\Actions\Fortify\UpdateUserPassword; -use App\Actions\Fortify\UpdateUserProfileInformation; use App\Models\OauthSetting; use App\Models\TeamInvitation; use App\Models\User; @@ -115,7 +114,6 @@ class FortifyServiceProvider extends ServiceProvider }); Fortify::resetUserPasswordsUsing(ResetUserPassword::class); - Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class); Fortify::updateUserPasswordsUsing(UpdateUserPassword::class); Fortify::confirmPasswordView(function () { diff --git a/config/fortify.php b/config/fortify.php index 6b78c1c65f..5b7864af01 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -136,7 +136,6 @@ return [ Features::registration(), Features::resetPasswords(), // Features::emailVerification(), - Features::updateProfileInformation(), Features::updatePasswords(), Features::twoFactorAuthentication([ 'confirm' => true, diff --git a/tests/Feature/FortifyProfileInformationRouteTest.php b/tests/Feature/FortifyProfileInformationRouteTest.php new file mode 100644 index 0000000000..340505232b --- /dev/null +++ b/tests/Feature/FortifyProfileInformationRouteTest.php @@ -0,0 +1,38 @@ + 0]); +}); + +it('does not let a user change the email through the Fortify profile route', function (bool $hasSsoIdentity) { + $user = User::factory()->create(['email' => 'owner@example.com']); + if ($hasSsoIdentity) { + OauthIdentity::create([ + 'user_id' => $user->id, + 'provider' => 'github', + 'issuer' => 'github', + 'provider_user_id' => '123', + 'email' => 'owner@example.com', + ]); + } + + $this->actingAs($user) + ->withoutMiddleware(VerifyCsrfToken::class) + ->put('/user/profile-information', ['name' => 'Changed', 'email' => 'attacker@example.com']); + + expect(Route::has('user-profile-information.update'))->toBeFalse() + ->and($user->fresh()->email)->toBe('owner@example.com') + ->and($user->fresh()->name)->not->toBe('Changed'); +})->with([ + 'SSO user' => [true], + 'password user' => [false], +]);