diff --git a/app/Livewire/Auth/ForgotPassword.php b/app/Livewire/Auth/ForgotPassword.php index 20f9fca..6fd8dea 100644 --- a/app/Livewire/Auth/ForgotPassword.php +++ b/app/Livewire/Auth/ForgotPassword.php @@ -29,6 +29,20 @@ class ForgotPassword extends Component return ! in_array(config('mail.default'), ['log', 'array', null], true); } + /** Secondary path: email a reset link (only meaningful when a real mailer is set). */ + public function sendResetLink(): void + { + if (! $this->mailEnabled()) { + return; + } + + $this->validate(['email' => ['required', 'email']]); + \Illuminate\Support\Facades\Password::sendResetLink(['email' => $this->email]); + + // Generic — never reveal whether the email exists. + $this->dispatch('notify', message: __('auth.reset_link_sent')); + } + /** Reset the password by proving 2FA possession (TOTP or a one-time backup code). */ public function resetPassword() { diff --git a/app/Livewire/Auth/ResetPassword.php b/app/Livewire/Auth/ResetPassword.php new file mode 100644 index 0000000..6718ba0 --- /dev/null +++ b/app/Livewire/Auth/ResetPassword.php @@ -0,0 +1,70 @@ +token = $token; + $this->email = (string) request()->query('email', ''); + } + + /** Consume the emailed reset token and set a new password. */ + public function resetPassword() + { + $this->validate([ + 'email' => ['required', 'email'], + 'password' => ['required', 'confirmed', PasswordRule::min(12)->mixedCase()->numbers()], + ]); + + $status = Password::reset( + [ + 'email' => $this->email, + 'password' => $this->password, + 'password_confirmation' => $this->password_confirmation, + 'token' => $this->token, + ], + function ($user) { + $user->forceFill([ + 'password' => Hash::make($this->password), + 'must_change_password' => false, + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + }, + ); + + if ($status !== Password::PasswordReset) { + throw ValidationException::withMessages(['email' => __('auth.reset_token_invalid')]); + } + + session()->flash('status', __('auth.reset_done')); + + return $this->redirect(route('login'), navigate: true); + } + + public function render() + { + return view('livewire.auth.reset-password')->title(__('auth.title_forgot')); + } +} diff --git a/resources/views/livewire/auth/reset-password.blade.php b/resources/views/livewire/auth/reset-password.blade.php new file mode 100644 index 0000000..7ee353c --- /dev/null +++ b/resources/views/livewire/auth/reset-password.blade.php @@ -0,0 +1,35 @@ +@php + $label = 'mb-1.5 block font-mono text-[11px] uppercase tracking-wider text-ink-3'; + $field = 'h-11 w-full rounded-md border border-line bg-inset px-3 text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none'; + $err = 'mt-1.5 flex items-center gap-1.5 font-mono text-[11px] text-offline'; +@endphp +
{{ __('auth.security') }}
+{{ __('auth.forgot_subtitle') }}
+