false: the app renders its own * screens (R1/R2) and Fortify keeps only the POST actions. */ #[Layout('layouts.portal')] class VerifyEmail extends Component { /** * Six a minute is generous for a person and useless for anything else. * Without it this is a button that makes the server send mail to an * arbitrary address as fast as it can be clicked. */ private const PER_MINUTE = 6; /** * Somebody already through has no step left, and telling them they do is * how a confirmed account goes looking for a mail it does not need. The * `verified` middleware only guards the other direction — it keeps the * unconfirmed out of the portal and has nothing to say about this page. */ public function mount() { if (Auth::user()?->hasVerifiedEmail()) { return $this->redirectRoute('dashboard', navigate: false); } return null; } public function resend(): void { $user = Auth::user(); // Already through — a stale tab, or the link opened in another window. // Sending another confirmation for an address that no longer needs one // is how somebody ends up chasing a mail they do not need. if ($user === null || $user->hasVerifiedEmail()) { $this->redirectRoute('dashboard', navigate: true); return; } $key = 'verify-resend:'.$user->getKey(); if (RateLimiter::tooManyAttempts($key, self::PER_MINUTE)) { $this->dispatch('notify', message: __('verify_email.notice_hint')); return; } RateLimiter::hit($key, 60); $user->sendEmailVerificationNotification(); $this->dispatch('notify', message: __('verify_email.notice_sent')); } /** * Throw this registration away. * * Only an UNCONFIRMED account, and only one nothing is attached to. The * webhook creates a customer's login from a paid checkout, and such a user * can sit here unconfirmed while already having a contract — deleting that * would be data loss dressed up as a convenience. It refuses and says where * to go instead. * * R23: the confirmation is a modal, not a browser dialog. This method is * what the modal's event reaches, and it re-checks everything itself * because a modal is reachable without this page's own guards. */ #[On('own-account-delete-confirmed')] public function deleteAccount() { $user = Auth::user(); if ($user === null) { return $this->redirectRoute('login', navigate: false); } if ($user->hasVerifiedEmail()) { // Confirmed accounts are closed, not deleted — that is a different // action, with an invoice history behind it (ConfirmCloseAccount). return $this->redirectRoute('dashboard', navigate: false); } if (Customer::query()->where('user_id', $user->getKey())->exists() || Customer::query()->where('email', $user->email)->exists()) { $this->dispatch('notify', message: __('verify_email.delete_has_contract')); return null; } Auth::guard('web')->logout(); $user->delete(); session()->invalidate(); session()->regenerateToken(); session()->flash('status', __('verify_email.deleted')); return $this->redirectRoute('register', navigate: false); } public function render() { return view('livewire.auth.verify-email', [ 'email' => Auth::user()?->getEmailForVerification() ?? '', ]); } }