diff --git a/app/Actions/Fortify/PasswordValidationRules.php b/app/Actions/Fortify/PasswordValidationRules.php new file mode 100644 index 000000000..76b19d330 --- /dev/null +++ b/app/Actions/Fortify/PasswordValidationRules.php @@ -0,0 +1,18 @@ +|string> + */ + protected function passwordRules(): array + { + return ['required', 'string', Password::default(), 'confirmed']; + } +} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php new file mode 100644 index 000000000..688d62f3b --- /dev/null +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -0,0 +1,28 @@ + $input + */ + public function reset(User $user, array $input): void + { + Validator::make($input, [ + 'password' => $this->passwordRules(), + ])->validate(); + + $user->forceFill([ + 'password' => $input['password'], + ])->save(); + } +} diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php deleted file mode 100644 index d610210f6..000000000 --- a/app/Http/Controllers/Auth/NewPasswordController.php +++ /dev/null @@ -1,69 +0,0 @@ - $request->email, - 'token' => $request->route('token'), - ]); - } - - /** - * Handle an incoming new password request. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function store(Request $request): RedirectResponse - { - $request->validate([ - 'token' => 'required', - 'email' => 'required|email', - 'password' => ['required', '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( - $request->only('email', 'password', 'password_confirmation', 'token'), - function (User $user) use ($request) { - $user->forceFill([ - 'password' => $request->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) { - return to_route('login')->with('status', __($status)); - } - - throw ValidationException::withMessages([ - 'email' => [__($status)], - ]); - } -} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php deleted file mode 100644 index 9fcfe49d0..000000000 --- a/app/Http/Controllers/Auth/PasswordResetLinkController.php +++ /dev/null @@ -1,41 +0,0 @@ - $request->session()->get('status'), - ]); - } - - /** - * Handle an incoming password reset link request. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function store(Request $request): RedirectResponse - { - $request->validate([ - 'email' => 'required|email', - ]); - - Password::sendResetLink( - $request->only('email') - ); - - return back()->with('status', __('A reset link will be sent if the account exists.')); - } -} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index e8fc91508..961914ce2 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -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; @@ -26,10 +27,19 @@ 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); + } + /** * Configure Fortify views. */ @@ -44,6 +54,15 @@ private function configureViews(): void 'status' => $request->session()->get('status'), ])); + Fortify::requestPasswordResetLinkView(fn (Request $request) => Inertia::render('auth/forgot-password', [ + 'status' => $request->session()->get('status'), + ])); + + Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/reset-password', [ + 'email' => $request->email, + 'token' => $request->route('token'), + ])); + Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/two-factor-challenge')); Fortify::confirmPasswordView(fn () => Inertia::render('auth/confirm-password')); diff --git a/config/fortify.php b/config/fortify.php index 0846d4498..9dd5999b2 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -145,10 +145,8 @@ 'features' => [ // Features::registration(), - // Features::resetPasswords(), + Features::resetPasswords(), Features::emailVerification(), - // Features::updateProfileInformation(), - // Features::updatePasswords(), Features::twoFactorAuthentication([ 'confirm' => true, 'confirmPassword' => true, diff --git a/resources/js/layouts/settings/layout.tsx b/resources/js/layouts/settings/layout.tsx index c128d82c5..1939af84e 100644 --- a/resources/js/layouts/settings/layout.tsx +++ b/resources/js/layouts/settings/layout.tsx @@ -3,9 +3,9 @@ import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; import { cn } from '@/lib/utils'; import { edit as editAppearance } from '@/routes/appearance'; -import { edit as editPassword } from '@/routes/password'; import { edit } from '@/routes/profile'; import { show } from '@/routes/two-factor'; +import { edit as editPassword } from '@/routes/user-password'; import { type NavItem } from '@/types'; import { Link } from '@inertiajs/react'; import { type PropsWithChildren } from 'react'; diff --git a/resources/js/pages/auth/forgot-password.tsx b/resources/js/pages/auth/forgot-password.tsx index 0e8803e28..3d32f16e9 100644 --- a/resources/js/pages/auth/forgot-password.tsx +++ b/resources/js/pages/auth/forgot-password.tsx @@ -1,6 +1,6 @@ // Components -import PasswordResetLinkController from '@/actions/App/Http/Controllers/Auth/PasswordResetLinkController'; import { login } from '@/routes'; +import { email } from '@/routes/password'; import { Form, Head } from '@inertiajs/react'; import { LoaderCircle } from 'lucide-react'; @@ -26,7 +26,7 @@ export default function ForgotPassword({ status }: { status?: string }) { )}
-
+ {({ processing, errors }) => ( <>
diff --git a/resources/js/pages/auth/reset-password.tsx b/resources/js/pages/auth/reset-password.tsx index d8a60d11a..142164d94 100644 --- a/resources/js/pages/auth/reset-password.tsx +++ b/resources/js/pages/auth/reset-password.tsx @@ -1,4 +1,4 @@ -import NewPasswordController from '@/actions/App/Http/Controllers/Auth/NewPasswordController'; +import { update } from '@/routes/password'; import { Form, Head } from '@inertiajs/react'; import InputError from '@/components/input-error'; @@ -22,7 +22,7 @@ export default function ResetPassword({ token, email }: ResetPasswordProps) { ({ ...data, token, email })} resetOnSuccess={['password', 'password_confirmation']} > diff --git a/resources/js/pages/settings/password.tsx b/resources/js/pages/settings/password.tsx index f5df1874b..4d1a45658 100644 --- a/resources/js/pages/settings/password.tsx +++ b/resources/js/pages/settings/password.tsx @@ -11,7 +11,7 @@ import HeadingSmall from '@/components/heading-small'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { edit } from '@/routes/password'; +import { edit } from '@/routes/user-password'; const breadcrumbs: BreadcrumbItem[] = [ { diff --git a/routes/auth.php b/routes/auth.php index 0f3bbf955..a03d112d6 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,7 +1,5 @@ name('register.store'); - - Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) - ->name('password.request'); - - Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) - ->name('password.email'); - - Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) - ->name('password.reset'); - - Route::post('reset-password', [NewPasswordController::class, 'store']) - ->name('password.store'); }); diff --git a/routes/settings.php b/routes/settings.php index 98dd9d730..5f40cc711 100644 --- a/routes/settings.php +++ b/routes/settings.php @@ -13,11 +13,11 @@ Route::patch('settings/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('settings/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); - Route::get('settings/password', [PasswordController::class, 'edit'])->name('password.edit'); + Route::get('settings/password', [PasswordController::class, 'edit'])->name('user-password.edit'); Route::put('settings/password', [PasswordController::class, 'update']) ->middleware('throttle:6,1') - ->name('password.update'); + ->name('user-password.update'); Route::get('settings/appearance', function () { return Inertia::render('settings/appearance'); diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php index 6737c2a76..42465ece2 100644 --- a/tests/Feature/Auth/PasswordResetTest.php +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -56,7 +56,7 @@ public function test_password_can_be_reset_with_valid_token() $this->post(route('password.email'), ['email' => $user->email]); Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { - $response = $this->post(route('password.store'), [ + $response = $this->post(route('password.update'), [ 'token' => $notification->token, 'email' => $user->email, 'password' => 'password', @@ -75,7 +75,7 @@ public function test_password_cannot_be_reset_with_invalid_token(): void { $user = User::factory()->create(); - $response = $this->post(route('password.store'), [ + $response = $this->post(route('password.update'), [ 'token' => 'invalid-token', 'email' => $user->email, 'password' => 'newpassword123', diff --git a/tests/Feature/Settings/PasswordUpdateTest.php b/tests/Feature/Settings/PasswordUpdateTest.php index c16652941..81cec8f2e 100644 --- a/tests/Feature/Settings/PasswordUpdateTest.php +++ b/tests/Feature/Settings/PasswordUpdateTest.php @@ -17,7 +17,7 @@ public function test_password_update_page_is_displayed() $response = $this ->actingAs($user) - ->get(route('password.edit')); + ->get(route('user-password.edit')); $response->assertStatus(200); } @@ -28,8 +28,8 @@ public function test_password_can_be_updated() $response = $this ->actingAs($user) - ->from(route('password.edit')) - ->put(route('password.update'), [ + ->from(route('user-password.edit')) + ->put(route('user-password.update'), [ 'current_password' => 'password', 'password' => 'new-password', 'password_confirmation' => 'new-password', @@ -37,7 +37,7 @@ public function test_password_can_be_updated() $response ->assertSessionHasNoErrors() - ->assertRedirect(route('password.edit')); + ->assertRedirect(route('user-password.edit')); $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); } @@ -48,8 +48,8 @@ public function test_correct_password_must_be_provided_to_update_password() $response = $this ->actingAs($user) - ->from(route('password.edit')) - ->put(route('password.update'), [ + ->from(route('user-password.edit')) + ->put(route('user-password.update'), [ 'current_password' => 'wrong-password', 'password' => 'new-password', 'password_confirmation' => 'new-password', @@ -57,6 +57,6 @@ public function test_correct_password_must_be_provided_to_update_password() $response ->assertSessionHasErrors('current_password') - ->assertRedirect(route('password.edit')); + ->assertRedirect(route('user-password.edit')); } }