Added email verified as requirement for non-local environments

This commit is contained in:
Constantin Graf
2024-04-15 19:24:57 +02:00
parent 7618b3d8a5
commit feb70910c7
6 changed files with 139 additions and 20 deletions

View File

@@ -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<string, mixed> $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<string, string> $input
*/
protected function updateVerifiedUser(User $user, array $input): void
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
'email_verified_at' => null,
])->save();
$user->sendEmailVerificationNotification();
}
}

View File

@@ -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,
];
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpFoundation\Response;
class EnsureEmailIsVerified
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next, ?string $redirectToRoute = null): Response
{
if (! app()->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);
}
}

View File

@@ -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<User> belongsToOrganization(Organization $organization)
* @method Builder<User> active()
*/
class User extends Authenticatable implements FilamentUser
class User extends Authenticatable implements FilamentUser, MustVerifyEmail
{
use HasApiTokens;
use HasFactory;

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Middleware;
use App\Http\Middleware\EnsureEmailIsVerified;
use App\Models\User;
use Illuminate\Support\Facades\Route;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
#[CoversClass(EnsureEmailIsVerified::class)]
#[UsesClass(EnsureEmailIsVerified::class)]
class EnsureEmailIsVerifiedMiddlewareTest extends MiddlewareTestAbstract
{
private function createTestRoute(): string
{
return Route::get('/test-route', function () {
return 'test-response';
})->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();
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Middleware;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
abstract class MiddlewareTestAbstract extends TestCase
{
use RefreshDatabase;
}