diff --git a/resources/views/livewire/pages/auth/forgot-password.blade.php b/resources/views/livewire/pages/auth/forgot-password.blade.php index 8d4112c6..03962031 100644 --- a/resources/views/livewire/pages/auth/forgot-password.blade.php +++ b/resources/views/livewire/pages/auth/forgot-password.blade.php @@ -1,8 +1,42 @@ - +validate([ + 'email' => ['required', 'string', 'email'], + ]); + + $status = Password::sendResetLink( + $this->only('email') + ); + + if ($status != Password::RESET_LINK_SENT) { + $this->addError('email', __($status)); + + return; + } + + $this->reset('email'); + + session()->flash('status', __($status)); + } +}; ?> + +
-
- +
+ + +

{{ __('pages/auth.forgot.page_title') }} @@ -11,10 +45,8 @@ {{ __('pages/auth.forgot.description') }}

- -
- @csrf - + +
{{ __('validation.attributes.email') }} @@ -24,6 +56,7 @@ type="text" id="email" name="email" + wire:model="email" autocomplete="email" required="true" :value="old('email')" @@ -42,4 +75,4 @@
- +
diff --git a/resources/views/livewire/pages/auth/login.blade.php b/resources/views/livewire/pages/auth/login.blade.php index 75a3ce10..cffecd5b 100644 --- a/resources/views/livewire/pages/auth/login.blade.php +++ b/resources/views/livewire/pages/auth/login.blade.php @@ -26,13 +26,16 @@ public function login(): void
-
+
+ +

{{ __('pages/auth.login.title') }}

+
diff --git a/resources/views/livewire/pages/auth/register.blade.php b/resources/views/livewire/pages/auth/register.blade.php index 4495bb08..959ed81a 100644 --- a/resources/views/livewire/pages/auth/register.blade.php +++ b/resources/views/livewire/pages/auth/register.blade.php @@ -25,9 +25,11 @@ public function register(): void ->mixedCase()], ]); - $validated['password'] = Hash::make($validated['password']); + $user = User::create($validated); - event(new Registered(User::create($validated))); + $user->assignRole('user'); + + event(new Registered($user)); session()->flash('status', __('pages/auth.register.email_verification_status')); } @@ -175,4 +177,4 @@ public function register(): void -
\ No newline at end of file +
diff --git a/resources/views/livewire/pages/auth/reset-password.blade.php b/resources/views/livewire/pages/auth/reset-password.blade.php index 433d15d4..c47ad7e3 100644 --- a/resources/views/livewire/pages/auth/reset-password.blade.php +++ b/resources/views/livewire/pages/auth/reset-password.blade.php @@ -1,54 +1,125 @@ - +token = $token; + + $this->email = request()->string('email'); + } + + /** + * Reset the password for the given user. + */ + public function resetPassword(): void + { + $this->validate([ + 'token' => ['required'], + 'email' => ['required', 'string', 'email'], + 'password' => ['required', 'string', 'confirmed', PasswordRules::min(8) + ->uncompromised() + ->numbers() + ->mixedCase()], + ]); + + $status = Password::reset( + $this->only('email', 'password', 'password_confirmation', 'token'), + function ($user) { + $user->forceFill([ + 'password' => Hash::make($this->password), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + ); + + if ($status != Password::PASSWORD_RESET) { + $this->addError('email', __($status)); + + return; + } + + Session::flash('status', __($status)); + + $this->redirectRoute('login', navigate: true); + } +}; ?> + +
+ + + + +

{{ __('pages/auth.reset.page_title') }}

- - @csrf - +
- - {{ __('validation.attributes.email') }} -
- - {{ __('validation.attributes.password') }} -
- - {{ __('validation.attributes.password_confirmation') }} -
@@ -63,4 +134,4 @@
- +
diff --git a/resources/views/livewire/pages/auth/verify-email.blade.php b/resources/views/livewire/pages/auth/verify-email.blade.php index 2ab84225..876e7b55 100644 --- a/resources/views/livewire/pages/auth/verify-email.blade.php +++ b/resources/views/livewire/pages/auth/verify-email.blade.php @@ -1,4 +1,35 @@ - +hasVerifiedEmail()) { + $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true); + + return; + } + + Auth::user()->sendEmailVerificationNotification(); + + Session::flash('status', 'verification-link-sent'); + } + + public function logout(Logout $logout): void + { + $logout(); + + $this->redirect('/', navigate: true); + } +}; ?> + +

@@ -44,4 +75,4 @@ class="text-sm text-gray-500 dark:text-gray-400 underline hover:text-gray-900 fo

- +
diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php new file mode 100644 index 00000000..ededd4dd --- /dev/null +++ b/tests/Feature/Auth/EmailVerificationTest.php @@ -0,0 +1,48 @@ +unverified()->create(); + + $response = $this->actingAs($user)->get('/verify-email'); + + $response->assertStatus(200); +}); + +test('email can be verified', function (): void { + $user = User::factory()->unverified()->create(); + + Event::fake(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); + + $response = $this->actingAs($user)->get($verificationUrl); + + Event::assertDispatched(Verified::class); + expect($user->fresh()->hasVerifiedEmail())->toBeTrue(); + $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); +}); + +test('email is not verified with invalid hash', function (): void { + $user = User::factory()->unverified()->create(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); + + $this->actingAs($user)->get($verificationUrl); + + expect($user->fresh()->hasVerifiedEmail())->toBeFalse(); +}); diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php new file mode 100644 index 00000000..2ca9ce48 --- /dev/null +++ b/tests/Feature/Auth/PasswordConfirmationTest.php @@ -0,0 +1,48 @@ +create(); + + $response = $this->actingAs($user)->get('/confirm-password'); + + $response + ->assertSeeVolt('pages.auth.confirm-password') + ->assertStatus(200); +}); + +test('password can be confirmed', function (): void { + $user = User::factory()->create(); + + $this->actingAs($user); + + $component = Volt::test('pages.auth.confirm-password') + ->set('password', 'password'); + + $component->call('confirmPassword'); + + $component + ->assertRedirect('/dashboard') + ->assertHasNoErrors(); +}); + +test('password is not confirmed with invalid password', function (): void { + $user = User::factory()->create(); + + $this->actingAs($user); + + $component = Volt::test('pages.auth.confirm-password') + ->set('password', 'wrong-password'); + + $component->call('confirmPassword'); + + $component + ->assertNoRedirect() + ->assertHasErrors('password'); +}); diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php new file mode 100644 index 00000000..ae18ea13 --- /dev/null +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -0,0 +1,75 @@ +get('/forgot-password'); + + $response + ->assertSeeVolt('pages.auth.forgot-password') + ->assertStatus(200); +}); + +test('reset password link can be requested', function (): void { + Notification::fake(); + + $user = User::factory()->create(); + + Volt::test('pages.auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); + + Notification::assertSentTo($user, ResetPassword::class); +}); + +test('reset password screen can be rendered', function (): void { + Notification::fake(); + + $user = User::factory()->create(); + + Volt::test('pages.auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $response = $this->get('/reset-password/'.$notification->token); + + $response + ->assertSeeVolt('pages.auth.reset-password') + ->assertStatus(200); + + return true; + }); +}); + +test('password can be reset with valid token', function (): void { + Notification::fake(); + + $user = User::factory()->create(); + + Volt::test('pages.auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $component = Volt::test('pages.auth.reset-password', ['token' => $notification->token]) + ->set('email', $user->email) + ->set('password', 'password') + ->set('password_confirmation', 'password'); + + $component->call('resetPassword'); + + $component + ->assertRedirect('/login') + ->assertHasNoErrors(); + + return true; + }); +}); diff --git a/tests/Feature/Auth/PasswordUpdateTest.php b/tests/Feature/Auth/PasswordUpdateTest.php new file mode 100644 index 00000000..3a0ff291 --- /dev/null +++ b/tests/Feature/Auth/PasswordUpdateTest.php @@ -0,0 +1,43 @@ +create(); + + $this->actingAs($user); + + $component = Volt::test('profile.update-password-form') + ->set('current_password', 'password') + ->set('password', 'new-password') + ->set('password_confirmation', 'new-password') + ->call('updatePassword'); + + $component + ->assertHasNoErrors() + ->assertNoRedirect(); + + $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); +}); + +test('correct password must be provided to update password', function (): void { + $user = User::factory()->create(); + + $this->actingAs($user); + + $component = Volt::test('profile.update-password-form') + ->set('current_password', 'wrong-password') + ->set('password', 'new-password') + ->set('password_confirmation', 'new-password') + ->call('updatePassword'); + + $component + ->assertHasErrors(['current_password']) + ->assertNoRedirect(); +});