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 @@ + + + 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 @@