Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/Actions/Fortify/PasswordValidationRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Actions\Fortify;

use Illuminate\Validation\Rules\Password;

trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
*/
protected function passwordRules(): array
{
return ['required', 'string', Password::default(), 'confirmed'];
}
}
28 changes: 28 additions & 0 deletions app/Actions/Fortify/ResetUserPassword.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Actions\Fortify;

use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\ResetsUserPasswords;

class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;

/**
* Validate and reset the user's forgotten password.
*
* @param array<string, string> $input
*/
public function reset(User $user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
])->validate();

$user->forceFill([
'password' => $input['password'],
])->save();
}
}
11 changes: 11 additions & 0 deletions app/Providers/FortifyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Providers;

use App\Actions\Fortify\ResetUserPassword;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
Expand All @@ -24,9 +25,17 @@ public function register(): void
*/
public function boot(): void
{
$this->configureActions();
$this->configureViews();
$this->configureRateLimiting();
}

/**
* Configure Fortify actions.
*/
private function configureActions(): void
{
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
}

/**
Expand All @@ -38,6 +47,8 @@ private function configureViews(): void
Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion config/fortify.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@

'features' => [
// Features::registration(),
// Features::resetPasswords(),
Features::resetPasswords(),
Features::emailVerification(),
// Features::updateProfileInformation(),
// Features::updatePasswords(),
Expand Down
2 changes: 1 addition & 1 deletion resources/views/components/settings/layout.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="me-10 w-full pb-4 md:w-[220px]">
<flux:navlist>
<flux:navlist.item :href="route('profile.edit')" wire:navigate>{{ __('Profile') }}</flux:navlist.item>
<flux:navlist.item :href="route('password.edit')" wire:navigate>{{ __('Password') }}</flux:navlist.item>
<flux:navlist.item :href="route('user-password.edit')" wire:navigate>{{ __('Password') }}</flux:navlist.item>
@if (Laravel\Fortify\Features::canManageTwoFactorAuthentication())
<flux:navlist.item :href="route('two-factor.show')" wire:navigate>{{ __('Two-Factor Auth') }}</flux:navlist.item>
@endif
Expand Down
80 changes: 30 additions & 50 deletions resources/views/livewire/auth/forgot-password.blade.php
Original file line number Diff line number Diff line change
@@ -1,51 +1,31 @@
<?php

use Illuminate\Support\Facades\Password;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;

new #[Layout('components.layouts.auth')] class extends Component {
public string $email = '';

/**
* Send a password reset link to the provided email address.
*/
public function sendPasswordResetLink(): void
{
$this->validate([
'email' => ['required', 'string', 'email'],
]);

Password::sendResetLink($this->only('email'));

session()->flash('status', __('A reset link will be sent if the account exists.'));
}
}; ?>

<div class="flex flex-col gap-6">
<x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />

<!-- Session Status -->
<x-auth-session-status class="text-center" :status="session('status')" />

<form method="POST" wire:submit="sendPasswordResetLink" class="flex flex-col gap-6">
<!-- Email Address -->
<flux:input
wire:model="email"
:label="__('Email Address')"
type="email"
required
autofocus
placeholder="email@example.com"
/>

<flux:button variant="primary" type="submit" class="w-full" data-test="email-password-reset-link-button">
{{ __('Email password reset link') }}
</flux:button>
</form>

<div class="space-x-1 rtl:space-x-reverse text-center text-sm text-zinc-400">
<span>{{ __('Or, return to') }}</span>
<flux:link :href="route('login')" wire:navigate>{{ __('log in') }}</flux:link>
<x-layouts.auth>
<div class="flex flex-col gap-6">
<x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />

<!-- Session Status -->
<x-auth-session-status class="text-center" :status="session('status')" />

<form method="POST" action="{{ route('password.email') }}" class="flex flex-col gap-6">
@csrf

<!-- Email Address -->
<flux:input
name="email"
:label="__('Email Address')"
type="email"
required
autofocus
placeholder="email@example.com"
/>

<flux:button variant="primary" type="submit" class="w-full" data-test="email-password-reset-link-button">
{{ __('Email password reset link') }}
</flux:button>
</form>

<div class="space-x-1 rtl:space-x-reverse text-center text-sm text-zinc-400">
<span>{{ __('Or, return to') }}</span>
<flux:link :href="route('login')" wire:navigate>{{ __('log in') }}</flux:link>
</div>
</div>
</div>
</x-layouts.auth>
167 changes: 52 additions & 115 deletions resources/views/livewire/auth/reset-password.blade.php
Original file line number Diff line number Diff line change
@@ -1,115 +1,52 @@
<?php

use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Volt\Component;

new #[Layout('components.layouts.auth')] class extends Component {
#[Locked]
public string $token = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';

/**
* Mount the component.
*/
public function mount(string $token): void
{
$this->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', Rules\Password::defaults()],
]);

// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$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 the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
if ($status !== Password::PasswordReset) {
$this->addError('email', __($status));

return;
}

Session::flash('status', __($status));

$this->redirectRoute('login', navigate: true);
}
}; ?>

<div class="flex flex-col gap-6">
<x-auth-header :title="__('Reset password')" :description="__('Please enter your new password below')" />

<!-- Session Status -->
<x-auth-session-status class="text-center" :status="session('status')" />

<form method="POST" wire:submit="resetPassword" class="flex flex-col gap-6">
<!-- Email Address -->
<flux:input
wire:model="email"
:label="__('Email')"
type="email"
required
autocomplete="email"
/>

<!-- Password -->
<flux:input
wire:model="password"
:label="__('Password')"
type="password"
required
autocomplete="new-password"
:placeholder="__('Password')"
viewable
/>

<!-- Confirm Password -->
<flux:input
wire:model="password_confirmation"
:label="__('Confirm password')"
type="password"
required
autocomplete="new-password"
:placeholder="__('Confirm password')"
viewable
/>

<div class="flex items-center justify-end">
<flux:button type="submit" variant="primary" class="w-full" data-test="reset-password-button">
{{ __('Reset password') }}
</flux:button>
</div>
</form>
</div>
<x-layouts.auth>
<div class="flex flex-col gap-6">
<x-auth-header :title="__('Reset password')" :description="__('Please enter your new password below')" />

<!-- Session Status -->
<x-auth-session-status class="text-center" :status="session('status')" />

<form method="POST" action="{{ route('password.update') }}" class="flex flex-col gap-6">
@csrf
<!-- Token -->
<input type="hidden" name="token" value="{{ request()->route('token') }}">

<!-- Email Address -->
<flux:input
name="email"
value="{{ request('email') }}"
:label="__('Email')"
type="email"
required
autocomplete="email"
/>

<!-- Password -->
<flux:input
name="password"
:label="__('Password')"
type="password"
required
autocomplete="new-password"
:placeholder="__('Password')"
viewable
/>

<!-- Confirm Password -->
<flux:input
name="password_confirmation"
:label="__('Confirm password')"
type="password"
required
autocomplete="new-password"
:placeholder="__('Confirm password')"
viewable
/>

<div class="flex items-center justify-end">
<flux:button type="submit" variant="primary" class="w-full" data-test="reset-password-button">
{{ __('Reset password') }}
</flux:button>
</div>
</form>
</div>
</x-layouts.auth>
6 changes: 0 additions & 6 deletions routes/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,4 @@
Route::middleware('guest')->group(function () {
Volt::route('register', 'auth.register')
->name('register');

Volt::route('forgot-password', 'auth.forgot-password')
->name('password.request');

Volt::route('reset-password/{token}', 'auth.reset-password')
->name('password.reset');
});
2 changes: 1 addition & 1 deletion routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Route::redirect('settings', 'settings/profile');

Volt::route('settings/profile', 'settings.profile')->name('profile.edit');
Volt::route('settings/password', 'settings.password')->name('password.edit');
Volt::route('settings/password', 'settings.password')->name('user-password.edit');
Volt::route('settings/appearance', 'settings.appearance')->name('appearance.edit');

Volt::route('settings/two-factor', 'settings.two-factor')
Expand Down
Loading