diff --git a/resources/views/components/confirms-password.blade.php b/resources/views/components/confirms-password.blade.php
new file mode 100644
index 000000000..220b5316a
--- /dev/null
+++ b/resources/views/components/confirms-password.blade.php
@@ -0,0 +1,46 @@
+@props(['title' => __('Confirm Password'), 'content' => __('For your security, please confirm your password to continue.'), 'button' => __('Confirm')])
+
+@php
+ $confirmableId = md5($attributes->wire('then'));
+@endphp
+
+wire('then') }}
+ x-data
+ x-ref="span"
+ x-on:click="$wire.startConfirmingPassword('{{ $confirmableId }}')"
+ x-on:password-confirmed.window="setTimeout(() => $event.detail.id === '{{ $confirmableId }}' && $refs.span.dispatchEvent(new CustomEvent('then', { bubbles: false })), 250);"
+>
+ {{ $slot }}
+
+
+@once
+
+
+ {{ $title }}
+
+
+
+ {{ $content }}
+
+
+
+
+
+
+
+
+
+
+ Nevermind
+
+
+
+ {{ $button }}
+
+
+
+@endonce
diff --git a/src/ConfirmsPasswords.php b/src/ConfirmsPasswords.php
new file mode 100644
index 000000000..e181ae2d3
--- /dev/null
+++ b/src/ConfirmsPasswords.php
@@ -0,0 +1,115 @@
+resetErrorBag();
+
+ if ($this->passwordIsConfirmed()) {
+ return $this->dispatchBrowserEvent('password-confirmed', [
+ 'id' => $confirmableId,
+ ]);
+ }
+
+ $this->confirmingPassword = true;
+ $this->confirmableId = $confirmableId;
+ $this->confirmablePassword = '';
+
+ $this->dispatchBrowserEvent('confirming-password');
+ }
+
+ /**
+ * Stop confirming the user's password.
+ *
+ * @return void
+ */
+ public function stopConfirmingPassword()
+ {
+ $this->confirmingPassword = false;
+ $this->confirmableId = null;
+ $this->confirmablePassword = '';
+ }
+
+ /**
+ * Confirm the user's password.
+ *
+ * @return void
+ */
+ public function confirmPassword()
+ {
+ if (! app(ConfirmPassword::class)(app(StatefulGuard::class), Auth::user(), $this->confirmablePassword)) {
+ throw ValidationException::withMessages([
+ 'confirmable_password' => [__('This password does not match our records.')],
+ ]);
+ }
+
+ session(['auth.password_confirmed_at' => time()]);
+
+ $this->dispatchBrowserEvent('password-confirmed', [
+ 'id' => $this->confirmableId,
+ ]);
+
+ $this->stopConfirmingPassword();
+ }
+
+ /**
+ * Ensure that the user's password has been recently confirmed.
+ *
+ * @param int|null $maximumSecondsSinceConfirmation
+ * @return void
+ */
+ protected function ensurePasswordIsConfirmed($maximumSecondsSinceConfirmation = null)
+ {
+ $maximumSecondsSinceConfirmation = $maximumSecondsSinceConfirmation ?: config('auth.password_timeout', 900);
+
+ return $this->passwordIsConfirmed($maximumSecondsSinceConfirmation) ? null : abort(403);
+ }
+
+ /**
+ * Determine if the user's password has been recently confirmed.
+ *
+ * @param int|null $maximumSecondsSinceConfirmation
+ * @return bool
+ */
+ protected function passwordIsConfirmed($maximumSecondsSinceConfirmation = null)
+ {
+ $maximumSecondsSinceConfirmation = $maximumSecondsSinceConfirmation ?: config('auth.password_timeout', 900);
+
+ return (time() - session('auth.password_confirmed_at', 0)) < $maximumSecondsSinceConfirmation;
+ }
+}
diff --git a/src/Http/Livewire/TwoFactorAuthenticationForm.php b/src/Http/Livewire/TwoFactorAuthenticationForm.php
index 74d05bff4..58688fa66 100644
--- a/src/Http/Livewire/TwoFactorAuthenticationForm.php
+++ b/src/Http/Livewire/TwoFactorAuthenticationForm.php
@@ -6,10 +6,13 @@
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
+use Laravel\Jetstream\ConfirmsPasswords;
use Livewire\Component;
class TwoFactorAuthenticationForm extends Component
{
+ use ConfirmsPasswords;
+
/**
* Indicates if two factor authentication QR code is being displayed.
*
@@ -32,12 +35,26 @@ class TwoFactorAuthenticationForm extends Component
*/
public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $enable)
{
+ $this->ensurePasswordIsConfirmed();
+
$enable(Auth::user());
$this->showingQrCode = true;
$this->showingRecoveryCodes = true;
}
+ /**
+ * Display the user's recovery codes.
+ *
+ * @return void
+ */
+ public function showRecoveryCodes()
+ {
+ $this->ensurePasswordIsConfirmed();
+
+ $this->showingRecoveryCodes = true;
+ }
+
/**
* Generate new recovery codes for the user.
*
@@ -46,6 +63,8 @@ public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $ena
*/
public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate)
{
+ $this->ensurePasswordIsConfirmed();
+
$generate(Auth::user());
$this->showingRecoveryCodes = true;
@@ -59,6 +78,8 @@ public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate)
*/
public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $disable)
{
+ $this->ensurePasswordIsConfirmed();
+
$disable(Auth::user());
}
diff --git a/src/JetstreamServiceProvider.php b/src/JetstreamServiceProvider.php
index fe2eb4fe0..7c7582ccd 100644
--- a/src/JetstreamServiceProvider.php
+++ b/src/JetstreamServiceProvider.php
@@ -94,6 +94,7 @@ protected function configureComponents()
$this->registerComponent('authentication-card-logo');
$this->registerComponent('button');
$this->registerComponent('confirmation-modal');
+ $this->registerComponent('confirms-password');
$this->registerComponent('danger-button');
$this->registerComponent('dialog-modal');
$this->registerComponent('dropdown');
diff --git a/stubs/inertia/resources/js/Jetstream/ConfirmsPassword.vue b/stubs/inertia/resources/js/Jetstream/ConfirmsPassword.vue
new file mode 100644
index 000000000..c0730505f
--- /dev/null
+++ b/stubs/inertia/resources/js/Jetstream/ConfirmsPassword.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
+
+
+ Nevermind
+
+
+
+ {{ button }}
+
+
+
+
+
+
+
diff --git a/stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue b/stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
index 655c39fff..ea087903a 100644
--- a/stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
+++ b/stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
@@ -52,31 +52,34 @@
-
- Enable
-
+
+
+ Enable
+
+
-
- Regenerate Recovery Codes
-
-
-
- Show Recovery Codes
-
-
-
- Disable
-
+
+
+ Regenerate Recovery Codes
+
+
+
+
+
+ Show Recovery Codes
+
+
+
+
+
+ Disable
+
+
@@ -86,6 +89,7 @@