diff --git a/resources/js/pages/auth/passwords/Reset.vue b/resources/js/pages/auth/passwords/Reset.vue index 4f1dccc8edc..eb88d40938a 100644 --- a/resources/js/pages/auth/passwords/Reset.vue +++ b/resources/js/pages/auth/passwords/Reset.vue @@ -30,9 +30,6 @@ const submit = () => { processing.value = true; errors.value = {}; }, - onSuccess: (e) => { - return window.location.href = e.url; - }, onError: () => processing.value = false }); } diff --git a/src/Auth/ResetsPasswords.php b/src/Auth/ResetsPasswords.php index 3716aecb789..05191ca4601 100644 --- a/src/Auth/ResetsPasswords.php +++ b/src/Auth/ResetsPasswords.php @@ -130,8 +130,6 @@ protected function resetPassword($user, $password) $user->save(); event(new PasswordReset($user)); - - $this->guard()->login($user); } /** diff --git a/src/Http/Controllers/CP/Auth/ResetPasswordController.php b/src/Http/Controllers/CP/Auth/ResetPasswordController.php index 338a5e501d7..db0bfb92cfa 100644 --- a/src/Http/Controllers/CP/Auth/ResetPasswordController.php +++ b/src/Http/Controllers/CP/Auth/ResetPasswordController.php @@ -25,6 +25,11 @@ public function broker() return Password::broker($broker); } + public function redirectPath() + { + return cp_route('login'); + } + protected function resetFormAction() { return route('statamic.cp.password.reset.action'); diff --git a/src/Http/Middleware/CP/HandleInertiaRequests.php b/src/Http/Middleware/CP/HandleInertiaRequests.php index 5d6f0f3ce44..ee44d2e00e3 100644 --- a/src/Http/Middleware/CP/HandleInertiaRequests.php +++ b/src/Http/Middleware/CP/HandleInertiaRequests.php @@ -75,6 +75,11 @@ private function toasts(Request $request) $this->toasts->success($message); } + // Laravel's built-in auth flows (password reset, etc.) flash to 'status'. + if ($message = $session->get('status')) { + $this->toasts->success($message); + } + if ($message = $session->get('error')) { $this->toasts->error($message); } diff --git a/tests/Auth/ResetPasswordTest.php b/tests/Auth/ResetPasswordTest.php new file mode 100644 index 00000000000..d02be1423ce --- /dev/null +++ b/tests/Auth/ResetPasswordTest.php @@ -0,0 +1,167 @@ + ['cp'], + 'web' => ['web'], + ]; + } + + private function resetUrl($type) + { + return match ($type) { + 'cp' => cp_route('password.reset.action'), + 'web' => route('statamic.password.reset.action'), + }; + } + + private function defaultRedirectUrl($type) + { + return match ($type) { + 'cp' => cp_route('login'), + 'web' => route('statamic.site'), + }; + } + + private function createUser() + { + return tap(User::make()->makeSuper()->email('san@holo.com')->password('secret'))->save(); + } + + private function enableTwoFactor($user) + { + $user->merge([ + 'two_factor_confirmed_at' => now()->timestamp, + 'two_factor_secret' => encrypt(app(TwoFactorAuthenticationProvider::class)->generateSecretKey()), + 'two_factor_recovery_codes' => encrypt(json_encode(Collection::times(8, function () { + return RecoveryCode::generate(); + })->all())), + ]); + + $user->save(); + + return $user; + } + + private function addPasskey($user) + { + $credential = PublicKeyCredentialSource::create( + publicKeyCredentialId: 'test-credential-id', + type: 'public-key', + transports: ['usb'], + attestationType: 'none', + trustPath: new EmptyTrustPath(), + aaguid: Uuid::fromString('00000000-0000-0000-0000-000000000000'), + credentialPublicKey: 'test-public-key', + userHandle: $user->id(), + counter: 0, + ); + + $passkey = (new Passkey)->setUser($user)->setName('Test Key')->setCredential($credential); + $passkey->save(); + + return $user->fresh(); + } + + private function createToken($user, $type) + { + $broker = config('statamic.users.passwords.'.PasswordReset::BROKER_RESETS); + + if (is_array($broker)) { + $broker = $broker[$type]; + } + + return Password::broker($broker)->createToken($user); + } + + #[Test] + #[DataProvider('resetPasswordProvider')] + public function it_resets_the_password_and_user_is_not_authenticated($type) + { + $user = $this->createUser(); + $token = $this->createToken($user, $type); + + $this + ->assertGuest() + ->post($this->resetUrl($type), [ + 'token' => $token, + 'email' => 'san@holo.com', + 'password' => 'newpassword', + 'password_confirmation' => 'newpassword', + ]) + ->assertSessionHas('status') + ->assertRedirect($this->defaultRedirectUrl($type)); + + $this->assertGuest(); + $this->assertTrue(Hash::check('newpassword', $user->fresh()->password())); + } + + #[Test] + #[DataProvider('resetPasswordProvider')] + public function it_resets_password_for_two_factor_user_and_user_is_not_authenticated($type) + { + $user = $this->enableTwoFactor($this->createUser()); + $token = $this->createToken($user, $type); + + $this + ->assertGuest() + ->post($this->resetUrl($type), [ + 'token' => $token, + 'email' => 'san@holo.com', + 'password' => 'newpassword', + 'password_confirmation' => 'newpassword', + ]) + ->assertSessionHas('status') + ->assertRedirect($this->defaultRedirectUrl($type)); + + $this->assertGuest(); + $this->assertTrue(Hash::check('newpassword', $user->fresh()->password())); + } + + #[Test] + #[DataProvider('resetPasswordProvider')] + public function it_resets_password_for_passkey_user_and_user_is_not_authenticated($type) + { + config(['statamic.webauthn.allow_password_login_with_passkey' => false]); + + $user = $this->addPasskey($this->createUser()); + $token = $this->createToken($user, $type); + + $this + ->assertGuest() + ->post($this->resetUrl($type), [ + 'token' => $token, + 'email' => 'san@holo.com', + 'password' => 'newpassword', + 'password_confirmation' => 'newpassword', + ]) + ->assertSessionHas('status') + ->assertRedirect($this->defaultRedirectUrl($type)); + + $this->assertGuest(); + $this->assertTrue(Hash::check('newpassword', $user->fresh()->password())); + } +}