feat(email): implement email change request and verification process
- Added functionality for users to request an email change, including generating a verification code and setting an expiration time. - Implemented methods in the User model to handle email change requests, code validation, and confirmation. - Created a new job to update the user's email in Stripe after confirmation. - Introduced rate limiting for email change requests and verification attempts to prevent abuse. - Added a new notification for email change verification. - Updated the profile component to manage email change requests and verification UI.
This commit is contained in:
@@ -53,6 +53,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
'email_verified_at' => 'datetime',
|
||||
'force_password_reset' => 'boolean',
|
||||
'show_boarding' => 'boolean',
|
||||
'email_change_code_expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
@@ -320,4 +321,77 @@ class User extends Authenticatable implements SendsEmail
|
||||
|
||||
return data_get($user, 'pivot.role');
|
||||
}
|
||||
|
||||
public function requestEmailChange(string $newEmail): void
|
||||
{
|
||||
// Generate 6-digit code
|
||||
$code = sprintf('%06d', mt_rand(0, 999999));
|
||||
|
||||
// Set expiration using config value
|
||||
$expiryMinutes = config('constants.email_change.verification_code_expiry_minutes', 10);
|
||||
$expiresAt = Carbon::now()->addMinutes($expiryMinutes);
|
||||
|
||||
$this->update([
|
||||
'pending_email' => $newEmail,
|
||||
'email_change_code' => $code,
|
||||
'email_change_code_expires_at' => $expiresAt,
|
||||
]);
|
||||
|
||||
// Send verification email to new address
|
||||
$this->notify(new \App\Notifications\TransactionalEmails\EmailChangeVerification($this, $code, $newEmail, $expiresAt));
|
||||
}
|
||||
|
||||
public function isEmailChangeCodeValid(string $code): bool
|
||||
{
|
||||
return $this->email_change_code === $code
|
||||
&& $this->email_change_code_expires_at
|
||||
&& Carbon::now()->lessThan($this->email_change_code_expires_at);
|
||||
}
|
||||
|
||||
public function confirmEmailChange(string $code): bool
|
||||
{
|
||||
if (! $this->isEmailChangeCodeValid($code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$oldEmail = $this->email;
|
||||
$newEmail = $this->pending_email;
|
||||
|
||||
// Update email and clear change request fields
|
||||
$this->update([
|
||||
'email' => $newEmail,
|
||||
'pending_email' => null,
|
||||
'email_change_code' => null,
|
||||
'email_change_code_expires_at' => null,
|
||||
]);
|
||||
|
||||
// For cloud users, dispatch job to update Stripe customer email asynchronously
|
||||
if (isCloud() && $this->currentTeam()->subscription) {
|
||||
dispatch(new \App\Jobs\UpdateStripeCustomerEmailJob(
|
||||
$this->currentTeam(),
|
||||
$this->id,
|
||||
$newEmail,
|
||||
$oldEmail
|
||||
));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clearEmailChangeRequest(): void
|
||||
{
|
||||
$this->update([
|
||||
'pending_email' => null,
|
||||
'email_change_code' => null,
|
||||
'email_change_code_expires_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasEmailChangeRequest(): bool
|
||||
{
|
||||
return ! is_null($this->pending_email)
|
||||
&& ! is_null($this->email_change_code)
|
||||
&& $this->email_change_code_expires_at
|
||||
&& Carbon::now()->lessThan($this->email_change_code_expires_at);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user