CluPilotCloud/app/Livewire/Auth/VerifyEmail.php

133 lines
4.5 KiB
PHP

<?php
namespace App\Livewire\Auth;
use App\Models\Customer;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Component;
/**
* Where somebody waits between registering and confirming.
*
* Signed in but not through: the account exists, and nothing in the portal
* opens until the address is confirmed. The page offers the two things that can
* move somebody forward — send it again, or throw the account away and start
* over with the address they meant.
*
* "Abmelden und neu registrieren" used to be the second one, and it was wrong
* twice: signing out does not free the address, so registering again with the
* same one fails — and every abandoned attempt stays in `users` for ever. The
* button deletes the account instead, and anything nobody comes back to is
* removed after PruneUnverifiedAccounts::AFTER_DAYS days.
*
* Fortify would own this page, but 'views' => 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() ?? '',
]);
}
}