From be89a4900dbc6a5c649f7aa057eccf09ede1983b Mon Sep 17 00:00:00 2001 From: Twyco Date: Thu, 10 Sep 2026 12:28:55 +0200 Subject: [PATCH] feat(KAN-22): rebuild password confirmation flows --- resources/js/locales/en.ts | 48 ++++ resources/js/pages/auth/ConfirmPassword.vue | 103 ++++--- .../js/pages/auth/ForcePasswordChange.vue | 255 +++++++++++------- resources/js/pages/auth/ForgotPassword.vue | 120 +++++---- resources/js/pages/auth/ResetPassword.vue | 196 +++++++++----- .../Feature/Auth/PasswordConfirmationTest.php | 26 +- tests/Feature/Auth/PasswordResetTest.php | 27 +- 7 files changed, 506 insertions(+), 269 deletions(-) diff --git a/resources/js/locales/en.ts b/resources/js/locales/en.ts index c7dd6d4..e3652e2 100644 --- a/resources/js/locales/en.ts +++ b/resources/js/locales/en.ts @@ -17,6 +17,54 @@ export const en = { 'auth.login.pending': 'Signing in…', 'auth.login.help': 'Need an account? Contact your workspace administrator.', 'auth.login.statusTitle': 'Account update', + 'auth.forgot.title': 'Reset your password', + 'auth.forgot.description': + 'Enter your account email and we will send you a secure reset link.', + 'auth.forgot.email': 'Email address', + 'auth.forgot.emailPlaceholder': 'you@example.com', + 'auth.forgot.emailHint': + 'Use the email address associated with your KanbanGit account.', + 'auth.forgot.submit': 'Send reset link', + 'auth.forgot.pending': 'Sending reset link…', + 'auth.forgot.sentTitle': 'Reset link sent', + 'auth.reset.title': 'Choose a new password', + 'auth.reset.description': + 'Create a new password for the account linked below.', + 'auth.reset.email': 'Account email', + 'auth.reset.password': 'New password', + 'auth.reset.passwordHint': + 'Use a password you do not use for another account.', + 'auth.reset.passwordConfirmation': 'Confirm new password', + 'auth.reset.submit': 'Save new password', + 'auth.reset.pending': 'Updating your password…', + 'auth.reset.invalidTitle': 'This reset link is no longer valid', + 'auth.reset.requestNew': 'Request a new reset link', + 'auth.reset.footer': + 'For your security, reset links expire and can only be used once.', + 'auth.confirm.title': 'Confirm it is you', + 'auth.confirm.description': + 'This action affects sensitive account information. Enter your password to continue.', + 'auth.confirm.password': 'Current password', + 'auth.confirm.passwordHint': + 'Your password is checked securely and is never saved in a draft.', + 'auth.confirm.submit': 'Confirm and continue', + 'auth.confirm.pending': 'Confirming your password…', + 'auth.confirm.footer': + 'Password confirmation expires automatically after a short time.', + 'auth.forceChange.title': 'Secure your account', + 'auth.forceChange.description': + 'You need to replace your temporary password before continuing to KanbanGit.', + 'auth.forceChange.currentPassword': 'Temporary password', + 'auth.forceChange.password': 'New password', + 'auth.forceChange.passwordHint': + 'Choose a password that differs from your temporary password.', + 'auth.forceChange.passwordConfirmation': 'Confirm new password', + 'auth.forceChange.submit': 'Save password and continue', + 'auth.forceChange.pending': 'Securing your account…', + 'auth.forceChange.statusTitle': 'Password update', + 'auth.forceChange.wrongAccount': 'Not the account you expected?', + 'auth.forceChange.footer': + 'Your password entries are kept in memory only and are cleared when you leave.', 'auth.verify.title': 'Check your inbox', 'auth.verify.description': 'Open the verification link in your email to finish confirming your account.', diff --git a/resources/js/pages/auth/ConfirmPassword.vue b/resources/js/pages/auth/ConfirmPassword.vue index 7932ef9..cc21735 100644 --- a/resources/js/pages/auth/ConfirmPassword.vue +++ b/resources/js/pages/auth/ConfirmPassword.vue @@ -1,52 +1,77 @@ diff --git a/resources/js/pages/auth/ForcePasswordChange.vue b/resources/js/pages/auth/ForcePasswordChange.vue index 29f9e79..e3c0b77 100644 --- a/resources/js/pages/auth/ForcePasswordChange.vue +++ b/resources/js/pages/auth/ForcePasswordChange.vue @@ -1,115 +1,170 @@ diff --git a/resources/js/pages/auth/ForgotPassword.vue b/resources/js/pages/auth/ForgotPassword.vue index b3200f6..92c12f2 100644 --- a/resources/js/pages/auth/ForgotPassword.vue +++ b/resources/js/pages/auth/ForgotPassword.vue @@ -1,65 +1,89 @@ diff --git a/resources/js/pages/auth/ResetPassword.vue b/resources/js/pages/auth/ResetPassword.vue index da1c85f..c01896f 100644 --- a/resources/js/pages/auth/ResetPassword.vue +++ b/resources/js/pages/auth/ResetPassword.vue @@ -1,88 +1,136 @@ diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php index 797e4f5..fb12e64 100644 --- a/tests/Feature/Auth/PasswordConfirmationTest.php +++ b/tests/Feature/Auth/PasswordConfirmationTest.php @@ -11,7 +11,7 @@ class PasswordConfirmationTest extends TestCase { use RefreshDatabase; - public function test_confirm_password_screen_can_be_rendered() + public function test_confirm_password_screen_can_be_rendered(): void { $user = User::factory()->create(); @@ -24,10 +24,32 @@ class PasswordConfirmationTest extends TestCase ); } - public function test_password_confirmation_requires_authentication() + public function test_password_confirmation_requires_authentication(): void { $response = $this->get(route('password.confirm')); $response->assertRedirect(route('login')); } + + public function test_password_can_be_confirmed(): void + { + $user = User::factory()->create(); + + $this->actingAs($user) + ->post(route('password.confirm'), ['password' => 'password']) + ->assertSessionHasNoErrors(); + + $this->assertNotNull(session('auth.password_confirmed_at')); + } + + public function test_password_confirmation_rejects_an_invalid_password(): void + { + $user = User::factory()->create(); + + $this->actingAs($user) + ->post(route('password.confirm'), ['password' => 'incorrect']) + ->assertSessionHasErrors('password'); + + $this->assertNull(session('auth.password_confirmed_at')); + } } diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php index f29c53d..b0bc024 100644 --- a/tests/Feature/Auth/PasswordResetTest.php +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -6,6 +6,7 @@ use App\Models\User; use Illuminate\Auth\Notifications\ResetPassword; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Notification; +use Inertia\Testing\AssertableInertia as Assert; use Laravel\Fortify\Features; use Tests\TestCase; @@ -20,11 +21,16 @@ class PasswordResetTest extends TestCase $this->skipUnlessFortifyFeature(Features::resetPasswords()); } - public function test_reset_password_link_screen_can_be_rendered() + public function test_reset_password_link_screen_can_be_rendered(): void { $response = $this->get(route('password.request')); - $response->assertOk(); + $response + ->assertOk() + ->assertInertia(fn (Assert $page) => $page + ->component('auth/ForgotPassword') + ->where('status', null), + ); } public function test_reset_password_link_can_be_requested() @@ -38,7 +44,7 @@ class PasswordResetTest extends TestCase Notification::assertSentTo($user, ResetPassword::class); } - public function test_reset_password_screen_can_be_rendered() + public function test_reset_password_screen_can_be_rendered(): void { Notification::fake(); @@ -46,10 +52,19 @@ class PasswordResetTest extends TestCase $this->post(route('password.email'), ['email' => $user->email]); - Notification::assertSentTo($user, ResetPassword::class, function ($notification) { - $response = $this->get(route('password.reset', $notification->token)); + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $response = $this->get(route('password.reset', [ + 'token' => $notification->token, + 'email' => $user->email, + ])); - $response->assertOk(); + $response + ->assertOk() + ->assertInertia(fn (Assert $page) => $page + ->component('auth/ResetPassword') + ->where('email', $user->email) + ->where('token', $notification->token), + ); return true; });