diff --git a/app/Actions/User/PasswordValidationRules.php b/app/Actions/User/PasswordValidationRules.php new file mode 100644 index 0000000..aed42bb --- /dev/null +++ b/app/Actions/User/PasswordValidationRules.php @@ -0,0 +1,18 @@ + + */ + protected function passwordRules(): array + { + return ['required', 'string', new Password, 'confirmed']; + } +} diff --git a/app/Actions/User/ResetUserPassword.php b/app/Actions/User/ResetUserPassword.php new file mode 100644 index 0000000..8e07b62 --- /dev/null +++ b/app/Actions/User/ResetUserPassword.php @@ -0,0 +1,29 @@ + $input + */ + public function reset(User $user, array $input): void + { + Validator::make($input, [ + 'password' => $this->passwordRules(), + ])->validate(); + + $user->forceFill([ + 'password' => Hash::make($input['password']), + ])->save(); + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php deleted file mode 100644 index 41a59ed..0000000 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ /dev/null @@ -1,48 +0,0 @@ -authenticate(); - - $request->session()->regenerate(); - - return redirect()->intended(RouteServiceProvider::HOME); - } - - /** - * Destroy an authenticated session. - */ - public function destroy(Request $request): RedirectResponse - { - Auth::guard('web')->logout(); - - $request->session()->invalidate(); - - $request->session()->regenerateToken(); - - return redirect()->route('login'); - } -} diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php deleted file mode 100644 index 523ddda..0000000 --- a/app/Http/Controllers/Auth/ConfirmablePasswordController.php +++ /dev/null @@ -1,41 +0,0 @@ -validate([ - 'email' => $request->user()->email, - 'password' => $request->password, - ])) { - throw ValidationException::withMessages([ - 'password' => __('auth.password'), - ]); - } - - $request->session()->put('auth.password_confirmed_at', time()); - - return redirect()->intended(RouteServiceProvider::HOME); - } -} diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php deleted file mode 100644 index 79ac3cd..0000000 --- a/app/Http/Controllers/Auth/NewPasswordController.php +++ /dev/null @@ -1,61 +0,0 @@ - $request]); - } - - /** - * 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) 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. - return $status == Password::PASSWORD_RESET - ? redirect()->route('login')->with('status', __($status)) - : back()->withInput($request->only('email')) - ->withErrors(['email' => __($status)]); - } -} diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php deleted file mode 100644 index 6916409..0000000 --- a/app/Http/Controllers/Auth/PasswordController.php +++ /dev/null @@ -1,29 +0,0 @@ -validateWithBag('updatePassword', [ - 'current_password' => ['required', 'current_password'], - 'password' => ['required', Password::defaults(), 'confirmed'], - ]); - - $request->user()->update([ - 'password' => Hash::make($validated['password']), - ]); - - return back()->with('status', 'password-updated'); - } -} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php deleted file mode 100644 index bf1ebfa..0000000 --- a/app/Http/Controllers/Auth/PasswordResetLinkController.php +++ /dev/null @@ -1,44 +0,0 @@ -validate([ - 'email' => ['required', 'email'], - ]); - - // We will send the password reset link to this user. Once we have attempted - // to send the link, we will examine the response then see the message we - // need to show to the user. Finally, we'll send out a proper response. - $status = Password::sendResetLink( - $request->only('email') - ); - - return $status == Password::RESET_LINK_SENT - ? back()->with('status', __($status)) - : back()->withInput($request->only('email')) - ->withErrors(['email' => __($status)]); - } -} diff --git a/app/Http/Livewire/Profile/TwoFactorAuthentication.php b/app/Http/Livewire/Profile/TwoFactorAuthentication.php new file mode 100644 index 0000000..02c5d84 --- /dev/null +++ b/app/Http/Livewire/Profile/TwoFactorAuthentication.php @@ -0,0 +1,14 @@ +user()->hasVerifiedEmail()) { - auth()->user()->sendEmailVerificationNotification(); + /** @var User $user */ + $user = auth()->user(); + if (! $user->hasVerifiedEmail()) { + $user->sendEmailVerificationNotification(); session()->flash('status', 'verification-link-sent'); } diff --git a/app/Models/User.php b/app/Models/User.php index 722c7e2..84942e0 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,6 +8,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; +use Laravel\Fortify\TwoFactorAuthenticatable; use Laravel\Sanctum\HasApiTokens; /** @@ -33,6 +34,7 @@ class User extends Authenticatable use HasApiTokens; use HasFactory; use Notifiable; + use TwoFactorAuthenticatable; protected $fillable = [ 'name', diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php new file mode 100644 index 0000000..5264c91 --- /dev/null +++ b/app/Providers/FortifyServiceProvider.php @@ -0,0 +1,63 @@ +input(Fortify::username())).'|'.$request->ip()); + + return Limit::perMinute(5)->by($throttleKey); + }); + + RateLimiter::for('two-factor', function (Request $request) { + return Limit::perMinute(5)->by($request->session()->get('login.id')); + }); + + Fortify::loginView(function () { + return view('auth.login'); + }); + + Fortify::requestPasswordResetLinkView(function () { + return view('auth.forgot-password'); + }); + + Fortify::resetPasswordView(function (Request $request) { + return view('auth.reset-password', [ + 'token' => $request->route('token'), + 'email' => $request->query('email'), + ]); + }); + + Fortify::confirmPasswordView(function () { + return view('auth.confirm-password'); + }); + + Fortify::twoFactorChallengeView(function () { + return view('auth.two-factor-challenge'); + }); + } +} diff --git a/composer.json b/composer.json index 70b8f6c..49baadc 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "aws/aws-sdk-php": "^3.158", "bensampo/laravel-enum": "^6.3", "guzzlehttp/guzzle": "^7.2", + "laravel/fortify": "^1.17", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", "laravel/socialite": "^5.2", diff --git a/composer.lock b/composer.lock index 7436369..ad49d9f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "289f9ab3a3999301701254ea3181abe3", + "content-hash": "0f725da8271d545b318e319f7b616053", "packages": [ { "name": "aws/aws-crt-php", @@ -154,6 +154,60 @@ }, "time": "2023-04-14T18:22:01+00:00" }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, { "name": "bensampo/laravel-enum", "version": "v6.3.1", @@ -437,6 +491,56 @@ ], "time": "2022-11-17T09:50:14+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + }, + "time": "2023-08-25T16:18:39+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.2", @@ -1416,6 +1520,70 @@ ], "time": "2023-03-08T11:55:01+00:00" }, + { + "name": "laravel/fortify", + "version": "v1.17.6", + "source": { + "type": "git", + "url": "https://github.com/laravel/fortify.git", + "reference": "dd1785363ef213e8d71be7949f24f7317b95e238" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/fortify/zipball/dd1785363ef213e8d71be7949f24f7317b95e238", + "reference": "dd1785363ef213e8d71be7949f24f7317b95e238", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "ext-json": "*", + "illuminate/support": "^8.82|^9.0|^10.0", + "php": "^7.3|^8.0", + "pragmarx/google2fa": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Fortify\\FortifyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Fortify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Backend controllers and scaffolding for Laravel authentication.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/fortify/issues", + "source": "https://github.com/laravel/fortify" + }, + "time": "2023-09-04T16:25:25+00:00" + }, { "name": "laravel/framework", "version": "v10.7.1", @@ -3301,6 +3469,58 @@ ], "time": "2023-07-09T15:24:48+00:00" }, + { + "name": "pragmarx/google2fa", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + }, + "time": "2022-06-13T21:57:56+00:00" + }, { "name": "psr/container", "version": "2.0.2", diff --git a/config/app.php b/config/app.php index 96311ad..5b4d798 100644 --- a/config/app.php +++ b/config/app.php @@ -185,6 +185,7 @@ /* * Package Service Providers... */ + App\Providers\FortifyServiceProvider::class, /* * Application Service Providers... diff --git a/config/fortify.php b/config/fortify.php new file mode 100644 index 0000000..e8212db --- /dev/null +++ b/config/fortify.php @@ -0,0 +1,147 @@ + 'web', + + /* + |-------------------------------------------------------------------------- + | Fortify Password Broker + |-------------------------------------------------------------------------- + | + | Here you may specify which password broker Fortify can use when a user + | is resetting their password. This configured value should match one + | of your password brokers setup in your "auth" configuration file. + | + */ + + 'passwords' => 'users', + + /* + |-------------------------------------------------------------------------- + | Username / Email + |-------------------------------------------------------------------------- + | + | This value defines which model attribute should be considered as your + | application's "username" field. Typically, this might be the email + | address of the users but you are free to change this value here. + | + | Out of the box, Fortify expects forgot password and reset password + | requests to have a field named 'email'. If the application uses + | another name for the field you may define it below as needed. + | + */ + + 'username' => 'email', + + 'email' => 'email', + + /* + |-------------------------------------------------------------------------- + | Home Path + |-------------------------------------------------------------------------- + | + | Here you may configure the path where users will get redirected during + | authentication or password reset when the operations are successful + | and the user is authenticated. You are free to change this value. + | + */ + + 'home' => RouteServiceProvider::HOME, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Prefix / Subdomain + |-------------------------------------------------------------------------- + | + | Here you may specify which prefix Fortify will assign to all the routes + | that it registers with the application. If necessary, you may change + | subdomain under which all of the Fortify routes will be available. + | + */ + + 'prefix' => '', + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Middleware + |-------------------------------------------------------------------------- + | + | Here you may specify which middleware Fortify will assign to the routes + | that it registers with the application. If necessary, you may change + | these middleware but typically this provided default is preferred. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Rate Limiting + |-------------------------------------------------------------------------- + | + | By default, Fortify will throttle logins to five requests per minute for + | every email and IP address combination. However, if you would like to + | specify a custom rate limiter to call then you may specify it here. + | + */ + + 'limiters' => [ + 'login' => 'login', + 'two-factor' => 'two-factor', + ], + + /* + |-------------------------------------------------------------------------- + | Register View Routes + |-------------------------------------------------------------------------- + | + | Here you may specify if the routes returning views should be disabled as + | you may not need them when building your own application. This may be + | especially true if you're writing a custom single-page application. + | + */ + + 'views' => true, + + /* + |-------------------------------------------------------------------------- + | Features + |-------------------------------------------------------------------------- + | + | Some of the Fortify features are optional. You may disable the features + | by removing them from this array. You're free to only remove some of + | these features or you can even remove all of these if you need to. + | + */ + + 'features' => [ + // Features::registration(), + Features::resetPasswords(), + // Features::emailVerification(), + Features::updateProfileInformation(), + Features::updatePasswords(), + Features::twoFactorAuthentication([ + // 'confirm' => true, + // 'confirmPassword' => true, + // 'window' => 0, + ]), + ], + +]; diff --git a/database/migrations/2023_09_10_185414_add_two_factor_fields_to_users_table.php b/database/migrations/2023_09_10_185414_add_two_factor_fields_to_users_table.php new file mode 100644 index 0000000..024ad27 --- /dev/null +++ b/database/migrations/2023_09_10_185414_add_two_factor_fields_to_users_table.php @@ -0,0 +1,32 @@ +timestamp('two_factor_confirmed_at') + ->after('two_factor_recovery_codes') + ->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn([ + 'two_factor_confirmed_at', + ]); + }); + } +}; diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php index a6494cc..f1011ee 100644 --- a/resources/views/auth/reset-password.blade.php +++ b/resources/views/auth/reset-password.blade.php @@ -1,14 +1,14 @@ -
+ @csrf - +
- +
diff --git a/resources/views/auth/two-factor-challenge.blade.php b/resources/views/auth/two-factor-challenge.blade.php new file mode 100644 index 0000000..f9b1105 --- /dev/null +++ b/resources/views/auth/two-factor-challenge.blade.php @@ -0,0 +1,50 @@ + +
+
+ + @csrf +
+ {{ __('Please enter your recovery code') }} +
+ +
+ + + +
+ +
+ + {{ __('Login') }} + + + {{ __('Recover') }} + +
+ +
+
+
+ @csrf +
+ {{ __('Please confirm access to your account by entering the authentication code provided by your authenticator application.') }} +
+ +
+ + + +
+ +
+ + {{ __('Recover') }} + + + {{ __('Login') }} + +
+
+
+
+
diff --git a/resources/views/livewire/profile/two-factor-authentication.blade.php b/resources/views/livewire/profile/two-factor-authentication.blade.php new file mode 100644 index 0000000..863ca60 --- /dev/null +++ b/resources/views/livewire/profile/two-factor-authentication.blade.php @@ -0,0 +1,60 @@ + + + {{ __('Two Factor Authentication') }} + + + + {{ __('Here you can activate 2FA to secure your account') }} + + + @if(! auth()->user()->two_factor_secret) + {{-- Enable 2FA --}} +
+ @csrf + + + {{ __('Enable Two-Factor') }} + +
+ @else + {{-- Disable 2FA --}} +
+ @csrf + @method('DELETE') + + + {{ __('Disable Two-Factor') }} + +
+ + @if(session('status') == 'two-factor-authentication-enabled') +
+ {{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.') }} +
+ +
+ {!! auth()->user()->twoFactorQrCodeSvg() !!} +
+ @endif + + {{-- Show 2FA Recovery Codes --}} +
+ {{ __('Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.') }} +
+ +
+ @foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code) +
{{ $code }}
+ @endforeach +
+ + {{-- Regenerate 2FA Recovery Codes --}} +
+ @csrf + + + {{ __('Regenerate Recovery Codes') }} + +
+ @endif +
diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php index 9684daf..d05eeb3 100644 --- a/resources/views/profile/index.blade.php +++ b/resources/views/profile/index.blade.php @@ -4,4 +4,6 @@ + + diff --git a/routes/auth.php b/routes/auth.php deleted file mode 100644 index e7f7c46..0000000 --- a/routes/auth.php +++ /dev/null @@ -1,24 +0,0 @@ -group(function () { - Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login'); - Route::post('login', [AuthenticatedSessionController::class, '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'); -}); - -Route::middleware('auth')->group(function () { - Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])->name('password.confirm'); - Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); - Route::put('password', [PasswordController::class, 'update'])->name('password.update'); - Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout'); -}); diff --git a/routes/web.php b/routes/web.php index b848e49..8b6794c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -54,5 +54,3 @@ }); }); }); - -require __DIR__.'/auth.php'; diff --git a/tests/Feature/Http/Auth/LogoutTest.php b/tests/Feature/Http/Auth/LogoutTest.php index 05fc0d2..5145cf1 100644 --- a/tests/Feature/Http/Auth/LogoutTest.php +++ b/tests/Feature/Http/Auth/LogoutTest.php @@ -13,7 +13,7 @@ public function test_logout(): void { $this->actingAs($this->user); - $this->post(route('logout'))->assertRedirect(route('login')); + $this->post(route('logout'))->assertRedirect('/'); $this->assertFalse(auth()->check()); } diff --git a/tests/Feature/Http/Auth/PasswordConfirmationTest.php b/tests/Feature/Http/Auth/PasswordConfirmationTest.php index 13e41a3..d693515 100644 --- a/tests/Feature/Http/Auth/PasswordConfirmationTest.php +++ b/tests/Feature/Http/Auth/PasswordConfirmationTest.php @@ -14,7 +14,7 @@ public function test_confirm_password_screen_can_be_rendered(): void { $this->actingAs($this->user); - $response = $this->get('/confirm-password'); + $response = $this->get(route('password.confirm')); $response->assertStatus(200); } @@ -26,7 +26,7 @@ public function test_password_can_be_confirmed(): void { $this->actingAs($this->user); - $response = $this->post('/confirm-password', [ + $response = $this->post(route('password.confirm'), [ 'password' => 'password', ]); @@ -38,7 +38,7 @@ public function test_password_is_not_confirmed_with_invalid_password(): void { $this->actingAs($this->user); - $response = $this->post('/confirm-password', [ + $response = $this->post(route('password.confirm'), [ 'password' => 'wrong-password', ]);