From 3acdc7a4a36fb54d42fe9245d05b9c67eb5d2e7b Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 13 Oct 2025 16:57:51 +0530 Subject: [PATCH 1/4] Replace reset password with fortify --- .../Fortify/PasswordValidationRules.php | 18 +++++ app/Actions/Fortify/ResetUserPassword.php | 28 ++++++++ .../Auth/NewPasswordController.php | 70 ------------------- .../Auth/PasswordResetLinkController.php | 41 ----------- app/Providers/FortifyServiceProvider.php | 19 +++++ config/fortify.php | 4 +- resources/js/layouts/settings/layout.tsx | 2 +- resources/js/pages/auth/forgot-password.tsx | 4 +- resources/js/pages/auth/reset-password.tsx | 4 +- resources/js/pages/settings/password.tsx | 2 +- routes/auth.php | 14 ---- routes/settings.php | 4 +- tests/Feature/Auth/PasswordResetTest.php | 4 +- tests/Feature/Settings/PasswordUpdateTest.php | 14 ++-- 14 files changed, 83 insertions(+), 145 deletions(-) create mode 100644 app/Actions/Fortify/PasswordValidationRules.php create mode 100644 app/Actions/Fortify/ResetUserPassword.php delete mode 100644 app/Http/Controllers/Auth/NewPasswordController.php delete mode 100644 app/Http/Controllers/Auth/PasswordResetLinkController.php 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 3496b1d3e..000000000 --- a/app/Http/Controllers/Auth/NewPasswordController.php +++ /dev/null @@ -1,70 +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' => Hash::make($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 c779977c4..9bb4abc42 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 { LoaderCircle } from 'lucide-react'; @@ -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')); } } From a777e8cde7c2df28f2d26b2b8c1ae2fcf435b701 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Tue, 14 Oct 2025 17:31:00 +0530 Subject: [PATCH 2/4] Replace register with fortify --- app/Actions/Fortify/CreateNewUser.php | 40 ++++++++++++++ .../Auth/RegisteredUserController.php | 53 ------------------- app/Providers/FortifyServiceProvider.php | 15 ++++-- config/fortify.php | 2 +- resources/js/pages/auth/login.tsx | 21 +++++--- resources/js/pages/auth/register.tsx | 4 +- resources/js/pages/welcome.tsx | 20 ++++--- routes/auth.php | 12 ----- routes/web.php | 6 ++- 9 files changed, 84 insertions(+), 89 deletions(-) create mode 100644 app/Actions/Fortify/CreateNewUser.php delete mode 100644 app/Http/Controllers/Auth/RegisteredUserController.php delete mode 100644 routes/auth.php diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php new file mode 100644 index 000000000..3d59821ee --- /dev/null +++ b/app/Actions/Fortify/CreateNewUser.php @@ -0,0 +1,40 @@ + $input + */ + public function create(array $input): User + { + Validator::make($input, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique(User::class), + ], + 'password' => $this->passwordRules(), + ])->validate(); + + return User::create([ + 'name' => $input['name'], + 'email' => $input['email'], + 'password' => $input['password'], + ]); + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php deleted file mode 100644 index c12868377..000000000 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ /dev/null @@ -1,53 +0,0 @@ -validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, - 'password' => ['required', 'confirmed', Rules\Password::defaults()], - ]); - - $user = User::create([ - 'name' => $request->name, - 'email' => $request->email, - 'password' => Hash::make($request->password), - ]); - - event(new Registered($user)); - - Auth::login($user); - - $request->session()->regenerate(); - - return redirect()->intended(route('dashboard', absolute: false)); - } -} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 961914ce2..968fe15cd 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Actions\Fortify\CreateNewUser; use App\Actions\Fortify\ResetUserPassword; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; @@ -38,6 +39,7 @@ public function boot(): void private function configureActions(): void { Fortify::resetUserPasswordsUsing(ResetUserPassword::class); + Fortify::createUsersUsing(CreateNewUser::class); } /** @@ -47,22 +49,25 @@ private function configureViews(): void { Fortify::loginView(fn (Request $request) => Inertia::render('auth/login', [ 'canResetPassword' => Features::enabled(Features::resetPasswords()), + 'canRegister' => Features::enabled(Features::registration()), 'status' => $request->session()->get('status'), ])); - Fortify::verifyEmailView(fn (Request $request) => Inertia::render('auth/verify-email', [ - 'status' => $request->session()->get('status'), + Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/reset-password', [ + 'email' => $request->email, + 'token' => $request->route('token'), ])); 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::verifyEmailView(fn (Request $request) => Inertia::render('auth/verify-email', [ + 'status' => $request->session()->get('status'), ])); + Fortify::registerView(fn () => Inertia::render('auth/register')); + 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 9dd5999b2..ce67e2c3a 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -144,7 +144,7 @@ */ 'features' => [ - // Features::registration(), + Features::registration(), Features::resetPasswords(), Features::emailVerification(), Features::twoFactorAuthentication([ diff --git a/resources/js/pages/auth/login.tsx b/resources/js/pages/auth/login.tsx index 7fff5f54c..beda09eb3 100644 --- a/resources/js/pages/auth/login.tsx +++ b/resources/js/pages/auth/login.tsx @@ -14,9 +14,14 @@ import { LoaderCircle } from 'lucide-react'; interface LoginProps { status?: string; canResetPassword: boolean; + canRegister: boolean; } -export default function Login({ status, canResetPassword }: LoginProps) { +export default function Login({ + status, + canResetPassword, + canRegister, +}: LoginProps) { return (
-
- Don't have an account?{' '} - - Sign up - -
+ {canRegister && ( +
+ Don't have an account?{' '} + + Sign up + +
+ )} )}
diff --git a/resources/js/pages/auth/register.tsx b/resources/js/pages/auth/register.tsx index 4015acae3..96a44ef9e 100644 --- a/resources/js/pages/auth/register.tsx +++ b/resources/js/pages/auth/register.tsx @@ -1,5 +1,5 @@ -import RegisteredUserController from '@/actions/App/Http/Controllers/Auth/RegisteredUserController'; import { login } from '@/routes'; +import { store } from '@/routes/register'; import { Form, Head } from '@inertiajs/react'; import { LoaderCircle } from 'lucide-react'; @@ -18,7 +18,7 @@ export default function Register() { >
().props; return ( @@ -32,12 +36,14 @@ export default function Welcome() { > Log in - - Register - + {canRegister && ( + + Register + + )} )} diff --git a/routes/auth.php b/routes/auth.php deleted file mode 100644 index a03d112d6..000000000 --- a/routes/auth.php +++ /dev/null @@ -1,12 +0,0 @@ -group(function () { - Route::get('register', [RegisteredUserController::class, 'create']) - ->name('register'); - - Route::post('register', [RegisteredUserController::class, 'store']) - ->name('register.store'); -}); diff --git a/routes/web.php b/routes/web.php index 5e4cebdf6..11e61c220 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,9 +2,12 @@ use Illuminate\Support\Facades\Route; use Inertia\Inertia; +use Laravel\Fortify\Features; Route::get('/', function () { - return Inertia::render('welcome'); + return Inertia::render('welcome', [ + 'canRegister' => Features::enabled(Features::registration()), + ]); })->name('home'); Route::middleware(['auth', 'verified'])->group(function () { @@ -14,4 +17,3 @@ }); require __DIR__.'/settings.php'; -require __DIR__.'/auth.php'; From 55a66951ff3837f5142924bb61029259d76727d2 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 20 Oct 2025 20:10:23 +0530 Subject: [PATCH 3/4] Add Hash pasword --- app/Actions/Fortify/CreateNewUser.php | 2 +- app/Actions/Fortify/ResetUserPassword.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 3d59821ee..7bf18d0a4 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -34,7 +34,7 @@ public function create(array $input): User return User::create([ 'name' => $input['name'], 'email' => $input['email'], - 'password' => $input['password'], + 'password' => Hash::make($input['password']), ]); } } diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php index 688d62f3b..7a57c5037 100644 --- a/app/Actions/Fortify/ResetUserPassword.php +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -3,6 +3,7 @@ namespace App\Actions\Fortify; use App\Models\User; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Laravel\Fortify\Contracts\ResetsUserPasswords; @@ -22,7 +23,7 @@ public function reset(User $user, array $input): void ])->validate(); $user->forceFill([ - 'password' => $input['password'], + 'password' => Hash::make($input['password']), ])->save(); } } From 55cf6124ff99ae144c615ec2e72b0e7a25c0adb8 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 20 Oct 2025 10:48:53 -0400 Subject: [PATCH 4/4] remove unnecessary hash call --- app/Actions/Fortify/CreateNewUser.php | 3 +-- app/Actions/Fortify/ResetUserPassword.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 7bf18d0a4..aebff5e4c 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -3,7 +3,6 @@ namespace App\Actions\Fortify; use App\Models\User; -use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Laravel\Fortify\Contracts\CreatesNewUsers; @@ -34,7 +33,7 @@ public function create(array $input): User return User::create([ 'name' => $input['name'], 'email' => $input['email'], - 'password' => Hash::make($input['password']), + 'password' => $input['password'], ]); } } diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php index 7a57c5037..688d62f3b 100644 --- a/app/Actions/Fortify/ResetUserPassword.php +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -3,7 +3,6 @@ namespace App\Actions\Fortify; use App\Models\User; -use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Laravel\Fortify\Contracts\ResetsUserPasswords; @@ -23,7 +22,7 @@ public function reset(User $user, array $input): void ])->validate(); $user->forceFill([ - 'password' => Hash::make($input['password']), + 'password' => $input['password'], ])->save(); } }