Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/Http/Controllers/UserEmailChangeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,15 @@ public function cancel(Request $request): RedirectResponse

return back()->with('email_change_status', 'Your pending email change has been cancelled.');
}

public function resend(Request $request): RedirectResponse
{
$user = $request->user();
$this->emailChangeService->resendConfirmation($user);

return back()->with(
'email_change_status',
'We sent another confirmation link to '.$user->fresh()->pending_email.'. Check that inbox (and spam folder).',
);
}
}
52 changes: 47 additions & 5 deletions app/Services/UserEmailChangeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,68 @@ public function requestChange(User $user, string $newEmail, ?string $password):
}
}

$token = Str::random(64);

$user->pending_email = $newEmail;
$token = $this->issuePendingToken($user);
$this->dispatchConfirmationEmail($user, $newEmail, $token);
$this->sendChangeNotification($user, $newEmail, $currentEmail);

return ['status' => 'confirmation_sent'];
}

/**
* @return array{status: string}
*/
public function resendConfirmation(User $user): array
{
$pendingEmail = SupportEmailAddress::normalize((string) ($user->pending_email ?? ''));
if ($pendingEmail === null || $user->pending_email_token === null) {
throw ValidationException::withMessages([
'pending_email' => 'There is no pending email change to confirm.',
]);
}

if ($this->emailInUseByAnotherUser($pendingEmail, (int) $user->id)) {
$this->cancelPending($user);

throw ValidationException::withMessages([
'new_email' => 'That email address is already in use on another CodeWeek account.',
]);
}

$token = $this->issuePendingToken($user);
$this->dispatchConfirmationEmail($user->fresh(), $pendingEmail, $token);

return ['status' => 'confirmation_resent'];
}

private function issuePendingToken(User $user): string
{
$token = Str::random(64);
$user->pending_email_token = hash('sha256', $token);
$user->pending_email_requested_at = now();
$user->save();

return $token;
}

private function dispatchConfirmationEmail(User $user, string $newEmail, string $token): void
{
$confirmUrl = URL::temporarySignedRoute(
'user.email-change.confirm',
now()->addHours(48),
['user' => $user->id, 'token' => $token],
);

Mail::to($newEmail)->queue(new PendingEmailChangeConfirmation($user, $confirmUrl));
}

if ($currentEmail !== null) {
Mail::to($currentEmail)->queue(new PendingEmailChangeNotification($user, $newEmail));
private function sendChangeNotification(User $user, string $newEmail, ?string $currentEmail): void
{
if ($currentEmail === null) {
return;
}

return ['status' => 'confirmation_sent'];
Mail::to($currentEmail)->queue(new PendingEmailChangeNotification($user, $newEmail));
}

public function cancelPending(User $user): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

Someone requested to change the login email on your EU Code Week account from **{{ $user->email }}** to **{{ $newEmail }}**.

If you made this request, no action is needed — the change will only take effect after the new address is confirmed.
**This is only a security notice.** The confirmation link was sent separately to **{{ $newEmail }}**. Open that inbox and click **Confirm new login email** to finish the change.

If you made this request, you can ignore this email and confirm from **{{ $newEmail }}** instead.

If you did **not** request this, please contact us immediately at info@codeweek.eu.

Expand Down
Loading
Loading