diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index feee9aef..cf355343 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -9,6 +9,7 @@ use App\Models\User; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +use Illuminate\Validation\ValidationException; use Laravel\Fortify\Contracts\UpdatesUserProfileInformation; class UpdateUserProfileInformation implements UpdatesUserProfileInformation @@ -17,6 +18,8 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation * Validate and update the given user's profile information. * * @param array $input + * + * @throws ValidationException */ public function update(User $user, array $input): void { @@ -34,30 +37,21 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) { - $this->updateVerifiedUser($user, $input); - } else { $user->forceFill([ 'name' => $input['name'], 'email' => $input['email'], + 'email_verified_at' => null, + 'timezone' => $input['timezone'], + 'week_start' => $input['week_start'], + ])->save(); + + $user->sendEmailVerificationNotification(); + } else { + $user->forceFill([ + 'name' => $input['name'], 'timezone' => $input['timezone'], 'week_start' => $input['week_start'], ])->save(); } } - - /** - * Update the given verified user's profile information. - * - * @param array $input - */ - protected function updateVerifiedUser(User $user, array $input): void - { - $user->forceFill([ - 'name' => $input['name'], - 'email' => $input['email'], - 'email_verified_at' => null, - ])->save(); - - $user->sendEmailVerificationNotification(); - } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 8526e5b4..818ffc02 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -69,6 +69,6 @@ class Kernel extends HttpKernel 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, 'signed' => \App\Http\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'verified' => \App\Http\Middleware\EnsureEmailIsVerified::class, ]; } diff --git a/app/Http/Middleware/EnsureEmailIsVerified.php b/app/Http/Middleware/EnsureEmailIsVerified.php new file mode 100644 index 00000000..d2ce9dee --- /dev/null +++ b/app/Http/Middleware/EnsureEmailIsVerified.php @@ -0,0 +1,33 @@ +isLocal()) { + if ($request->user() === null || + ($request->user() instanceof MustVerifyEmail && + ! $request->user()->hasVerifiedEmail())) { + return $request->expectsJson() + ? abort(403, 'Your email address is not verified.') + : Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice')); + } + } + + return $next($request); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index cc44c244..9c737c52 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,6 +8,7 @@ use App\Enums\Weekday; use Database\Factories\UserFactory; use Filament\Models\Contracts\FilamentUser; use Filament\Panel; +use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -40,7 +41,7 @@ use Laravel\Passport\HasApiTokens; * @method Builder belongsToOrganization(Organization $organization) * @method Builder active() */ -class User extends Authenticatable implements FilamentUser +class User extends Authenticatable implements FilamentUser, MustVerifyEmail { use HasApiTokens; use HasFactory; diff --git a/tests/Unit/Middleware/EnsureEmailIsVerifiedMiddlewareTest.php b/tests/Unit/Middleware/EnsureEmailIsVerifiedMiddlewareTest.php new file mode 100644 index 00000000..a7aae965 --- /dev/null +++ b/tests/Unit/Middleware/EnsureEmailIsVerifiedMiddlewareTest.php @@ -0,0 +1,78 @@ +middleware(EnsureEmailIsVerified::class)->uri; + } + + public function test_guests_are_redirected_to_verification_notice_route(): void + { + // Arrange + $route = $this->createTestRoute(); + + // Act + $response = $this->get($route); + + // Assert + $response->assertRedirect(route('verification.notice')); + } + + public function test_users_with_unverified_email_are_redirected_to_verification_notice_route(): void + { + // Arrange + $user = User::factory()->unverified()->create(); + $route = $this->createTestRoute(); + $this->actingAs($user); + + // Act + $response = $this->get($route); + + // Assert + $response->assertRedirect(route('verification.notice')); + } + + public function test_users_with_verified_email_can_access_route(): void + { + // Arrange + $user = User::factory()->create(); + $route = $this->createTestRoute(); + $this->actingAs($user); + + // Act + $response = $this->get($route); + + // Assert + $response->assertOk(); + } + + public function test_users_with_unverified_email_can_access_route_in_local_environment(): void + { + // Arrange + $user = User::factory()->unverified()->create(); + $route = $this->createTestRoute(); + $this->actingAs($user); + $this->app->detectEnvironment(fn () => 'local'); + + // Act + $response = $this->get($route); + + // Assert + $response->assertOk(); + } +} diff --git a/tests/Unit/Middleware/MiddlewareTestAbstract.php b/tests/Unit/Middleware/MiddlewareTestAbstract.php new file mode 100644 index 00000000..a24e94ee --- /dev/null +++ b/tests/Unit/Middleware/MiddlewareTestAbstract.php @@ -0,0 +1,13 @@ +