This commit is contained in:
Dan Harrin
2022-11-01 12:35:09 +00:00
parent 2970960106
commit 8aab1f3ffd
11 changed files with 504 additions and 9 deletions
@@ -27,7 +27,7 @@ return [
],
'messages' => [
'throttled' => 'Too many reset attempts. Please try again in :seconds seconds.',
'throttled' => 'Too many requests. Please try again in :seconds seconds.',
],
];
+8 -1
View File
@@ -217,7 +217,14 @@ class Context
public function emailVerification(?string $promptPage = EmailVerificationPrompt::class, bool $isRequired = true): static
{
$this->emailVerificationPromptPage = $promptPage;
$this->isEmailVerificationRequired = $isRequired;
$this->requiresEmailVerification($isRequired);
return $this;
}
public function requiresEmailVerification(bool $condition = true): static
{
$this->isEmailVerificationRequired = $condition;
return $this;
}
@@ -19,6 +19,7 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password as PasswordRule;
use Livewire\Livewire;
/**
* @property Form $form
@@ -106,9 +107,6 @@ class ResetPassword extends CardPage
->schema([
TextInput::make('email')
->label(__('filament::pages/auth/password-reset/reset-password.fields.email.label'))
->email()
->required()
->maxLength(255)
->disabled(),
TextInput::make('password')
->label(__('filament::pages/auth/password-reset/reset-password.fields.password.label'))
@@ -126,7 +124,7 @@ class ResetPassword extends CardPage
public function propertyIsPublicAndNotDefinedOnBaseClass($propertyName): bool
{
if (in_array($propertyName, [
if ((! app()->runningUnitTests()) && in_array($propertyName, [
'email',
'token',
])) {
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration
{
public function up(): void
{
Schema::create('password_resets', function (Blueprint $table): void {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
public function down(): void
{
Schema::dropIfExists('password_resets');
}
};
@@ -0,0 +1,59 @@
<?php
use Filament\Facades\Filament;
use Filament\Notifications\Auth\VerifyEmail;
use Filament\Pages\Auth\EmailVerification\EmailVerificationPrompt;
use Filament\Tests\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Support\Facades\Notification;
use function Pest\Livewire\livewire;
uses(TestCase::class);
it('can render page', function () {
$userToVerify = User::factory()->create([
'email_verified_at' => null,
]);
$this->actingAs($userToVerify);
$this->get(Filament::getEmailVerificationPromptUrl())->assertSuccessful();
});
it('can resend notification', function () {
Notification::fake();
$userToVerify = User::factory()->create([
'email_verified_at' => null,
]);
$this->actingAs($userToVerify);
livewire(EmailVerificationPrompt::class)
->call('resendNotification')
->assertNotified();
Notification::assertSentTo($userToVerify, VerifyEmail::class);
});
it('can throttle resend notification attempts', function () {
Notification::fake();
$userToVerify = User::factory()->create([
'email_verified_at' => null,
]);
$this->actingAs($userToVerify);
livewire(EmailVerificationPrompt::class)
->call('resendNotification')
->assertNotified();
Notification::assertSentToTimes($userToVerify, VerifyEmail::class, times: 1);
livewire(EmailVerificationPrompt::class)
->call('resendNotification')
->assertNotified();
Notification::assertSentToTimes($userToVerify, VerifyEmail::class, times: 1);
});
@@ -0,0 +1,26 @@
<?php
use Filament\Facades\Filament;
use Filament\Http\Responses\Auth\Contracts\LogoutResponse;
use Filament\Tests\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Http\RedirectResponse;
uses(TestCase::class);
it('can verify an email', function () {
$userToVerify = User::factory()->create([
'email_verified_at' => null,
]);
expect($userToVerify)
->hasVerifiedEmail()->toBeFalse();
$this
->actingAs($userToVerify)
->get(Filament::getVerifyEmailUrl($userToVerify))
->assertRedirect(Filament::getUrl());
expect($userToVerify->refresh())
->hasVerifiedEmail()->toBeTrue();
});
+3 -3
View File
@@ -22,7 +22,7 @@ it('can authenticate', function () {
->set('email', $userToAuthenticate->email)
->set('password', 'password')
->call('authenticate')
->assertRedirect(route('filament.admin.pages.dashboard'));
->assertRedirect(Filament::getUrl());
$this->assertAuthenticatedAs($userToAuthenticate);
});
@@ -82,7 +82,7 @@ it('can throttle authentication attempts', function () {
it('can validate `email` is required', function () {
livewire(Login::class)
->assertSet('email', '')
->set('email', '')
->call('authenticate')
->assertHasErrors(['email' => ['required']]);
});
@@ -96,7 +96,7 @@ it('can validate `email` is valid email', function () {
it('can validate `password` is required', function () {
livewire(Login::class)
->assertSet('password', '')
->set('password', '')
->call('authenticate')
->assertHasErrors(['password' => ['required']]);
});
@@ -0,0 +1,66 @@
<?php
use Filament\Facades\Filament;
use Filament\Notifications\Auth\ResetPassword;
use Filament\Pages\Auth\PasswordReset\RequestPasswordReset;
use Filament\Tests\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Support\Facades\Notification;
use function Pest\Livewire\livewire;
uses(TestCase::class);
it('can render page', function () {
$this->get(Filament::getRequestPasswordResetUrl())->assertSuccessful();
});
it('can request password reset', function () {
Notification::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
livewire(RequestPasswordReset::class)
->set('email', $userToResetPassword->email)
->call('request')
->assertNotified();
Notification::assertSentTo($userToResetPassword, ResetPassword::class);
});
it('can throttle requests', function () {
Notification::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
livewire(RequestPasswordReset::class)
->set('email', $userToResetPassword->email)
->call('request')
->assertNotified();
Notification::assertSentToTimes($userToResetPassword, ResetPassword::class, times: 1);
livewire(RequestPasswordReset::class)
->set('email', $userToResetPassword->email)
->call('request')
->assertNotified();
Notification::assertSentToTimes($userToResetPassword, ResetPassword::class, times: 1);
});
it('can validate `email` is required', function () {
livewire(RequestPasswordReset::class)
->set('email', '')
->call('request')
->assertHasErrors(['email' => ['required']]);
});
it('can validate `email` is valid email', function () {
livewire(RequestPasswordReset::class)
->set('email', 'invalid-email')
->call('request')
->assertHasErrors(['email' => ['email']]);
});
@@ -0,0 +1,155 @@
<?php
use Filament\Facades\Filament;
use Filament\Pages\Auth\PasswordReset\ResetPassword;
use Filament\Pages\Auth\Register;
use Filament\Tests\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use function Pest\Livewire\livewire;
uses(TestCase::class);
it('can render page', function () {
$userToResetPassword = User::factory()->make();
$token = Password::createToken($userToResetPassword);
$this->get(Filament::getResetPasswordUrl(
$token,
$userToResetPassword,
))->assertSuccessful();
});
it('can reset password', function () {
Event::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
$token = Password::createToken($userToResetPassword);
livewire(ResetPassword::class)
->set('email', $userToResetPassword->email)
->set('token', $token)
->set('password', 'new-password')
->set('passwordConfirmation', 'new-password')
->call('resetPassword')
->assertNotified()
->assertRedirect(Filament::getLoginUrl());
Event::assertDispatched(PasswordReset::class);
$this->assertCredentials([
'email' => $userToResetPassword->email,
'password' => 'new-password',
]);
});
it('requires request signature', function () {
$userToResetPassword = User::factory()->make();
$token = Password::createToken($userToResetPassword);
$this->get(route("filament.admin.auth.password-reset.reset", [
'email' => $userToResetPassword->getEmailForPasswordReset(),
'token' => $token,
]))->assertForbidden();
});
it('requires valid email and token', function () {
Event::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
$token = Password::createToken($userToResetPassword);
livewire(ResetPassword::class)
->set('email', $userToResetPassword->email)
->set('token', Str::random())
->set('password', 'new-password')
->set('passwordConfirmation', 'new-password')
->call('resetPassword')
->assertNotified()
->assertNoRedirect();
Event::assertNotDispatched(PasswordReset::class);
livewire(ResetPassword::class)
->set('email', fake()->email())
->set('token', $token)
->set('password', 'new-password')
->set('passwordConfirmation', 'new-password')
->call('resetPassword')
->assertNotified()
->assertNoRedirect();
Event::assertNotDispatched(PasswordReset::class);
});
it('can throttle reset password attempts', function () {
Event::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
$token = Password::createToken($userToResetPassword);
livewire(ResetPassword::class)
->set('email', $userToResetPassword->email)
->set('token', $token)
->set('password', 'new-password')
->set('passwordConfirmation', 'new-password')
->call('resetPassword')
->assertNotified()
->assertRedirect(Filament::getLoginUrl());
Event::assertDispatchedTimes(PasswordReset::class, times: 1);
$this->assertCredentials([
'email' => $userToResetPassword->email,
'password' => 'new-password',
]);
livewire(ResetPassword::class)
->set('email', $userToResetPassword->email)
->set('token', $token)
->set('password', 'newer-password')
->set('passwordConfirmation', 'newer-password')
->call('resetPassword')
->assertNotified()
->assertNoRedirect();
Event::assertDispatchedTimes(PasswordReset::class, times: 1);
$this->assertCredentials([
'email' => $userToResetPassword->email,
'password' => 'new-password',
]);
});
it('can validate `password` is required', function () {
livewire(ResetPassword::class)
->set('password', '')
->call('resetPassword')
->assertHasErrors(['password' => ['required']]);
});
it('can validate `password` is confirmed', function () {
livewire(ResetPassword::class)
->set('password', Str::random())
->set('passwordConfirmation', Str::random())
->call('resetPassword')
->assertHasErrors(['password' => ['same']]);
});
it('can validate `passwordConfirmation` is required', function () {
livewire(ResetPassword::class)
->set('passwordConfirmation', '')
->call('resetPassword')
->assertHasErrors(['passwordConfirmation' => ['required']]);
});
+160
View File
@@ -0,0 +1,160 @@
<?php
use Filament\Facades\Filament;
use Filament\Pages\Auth\Register;
use Filament\Tests\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use function Pest\Livewire\livewire;
uses(TestCase::class);
it('can render page', function () {
$this->get(Filament::getRegistrationUrl())->assertSuccessful();
});
it('can register', function () {
Event::fake();
$this->assertGuest();
Filament::getCurrentContext()->requiresEmailVerification(false);
$userToRegister = User::factory()->make();
livewire(Register::class)
->set('name', $userToRegister->name)
->set('email', $userToRegister->email)
->set('password', 'password')
->set('passwordConfirmation', 'password')
->call('register')
->assertNotified()
->assertRedirect(Filament::getUrl());
Event::assertDispatched(Registered::class);
$this->assertAuthenticated();
$this->assertCredentials([
'email' => $userToRegister->email,
'password' => 'password',
]);
});
it('can register and redirect user to their intended URL', function () {
session()->put('url.intended', $intendedUrl = Str::random());
Filament::getCurrentContext()->requiresEmailVerification(false);
$userToRegister = User::factory()->make();
livewire(Register::class)
->set('name', $userToRegister->name)
->set('email', $userToRegister->email)
->set('password', 'password')
->set('passwordConfirmation', 'password')
->call('register')
->assertRedirect($intendedUrl);
});
it('can throttle registration attempts', function () {
Event::fake();
$this->assertGuest();
$userToRegister = User::factory()->make();
livewire(Register::class)
->set('name', $userToRegister->name)
->set('email', $userToRegister->email)
->set('password', 'password')
->set('passwordConfirmation', 'password')
->call('register')
->assertNotified();
Event::assertDispatchedTimes(Registered::class, times: 1);
$this->assertAuthenticated();
auth()->logout();
livewire(Register::class)
->set('name', $userToRegister->name)
->set('email', $userToRegister->email)
->set('password', 'password')
->set('passwordConfirmation', 'password')
->call('register')
->assertNotified();
Event::assertDispatchedTimes(Registered::class, times: 1);
$this->assertGuest();
});
it('can validate `name` is required', function () {
livewire(Register::class)
->set('name', '')
->call('register')
->assertHasErrors(['name' => ['required']]);
});
it('can validate `name` is max 255 characters', function () {
livewire(Register::class)
->set('name', Str::random(256))
->call('register')
->assertHasErrors(['name' => ['max']]);
});
it('can validate `email` is required', function () {
livewire(Register::class)
->set('email', '')
->call('register')
->assertHasErrors(['email' => ['required']]);
});
it('can validate `email` is valid email', function () {
livewire(Register::class)
->set('email', 'invalid-email')
->call('register')
->assertHasErrors(['email' => ['email']]);
});
it('can validate `email` is max 255 characters', function () {
livewire(Register::class)
->set('email', Str::random(256))
->call('register')
->assertHasErrors(['email' => ['max']]);
});
it('can validate `email` is unique', function () {
$existingEmail = User::factory()->create()->email;
livewire(Register::class)
->set('email', $existingEmail)
->call('register')
->assertHasErrors(['email' => ['unique']]);
});
it('can validate `password` is required', function () {
livewire(Register::class)
->set('password', '')
->call('register')
->assertHasErrors(['password' => ['required']]);
});
it('can validate `password` is confirmed', function () {
livewire(Register::class)
->set('password', Str::random())
->set('passwordConfirmation', Str::random())
->call('register')
->assertHasErrors(['password' => ['same']]);
});
it('can validate `passwordConfirmation` is required', function () {
livewire(Register::class)
->set('passwordConfirmation', '')
->call('register')
->assertHasErrors(['passwordConfirmation' => ['required']]);
});
+2
View File
@@ -8,10 +8,12 @@ use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements FilamentUser, MustVerifyEmail
{
use HasFactory;
use Notifiable;
protected $guarded = [];