where('id', '!=', $user->id)->exists()) { throw ValidationException::withMessages([ 'new_email' => ['Dit e-mailadres is al in gebruik door een ander account.'], ]); } // Cancel any existing pending requests for this user EmailChangeRequest::where('user_id', $user->id) ->where('status', EmailChangeStatus::PENDING) ->update(['status' => EmailChangeStatus::CANCELLED->value]); // Generate secure token $plainToken = Str::random(64); $request = EmailChangeRequest::create([ 'user_id' => $user->id, 'current_email' => $user->email, 'new_email' => $newEmail, 'token' => hash('sha256', $plainToken), 'requested_by_user_id' => $requestedBy->id, 'status' => EmailChangeStatus::PENDING, 'expires_at' => now()->addHours(24), ]); // Send verification email to the NEW address Mail::to($newEmail)->send(new VerifyEmailChangeMail( user: $user, newEmail: $newEmail, token: $plainToken, frontendUrl: $frontendUrl, requestedBy: $requestedBy, )); activity() ->causedBy($requestedBy) ->performedOn($user) ->withProperties([ 'current_email' => $user->email, 'new_email' => $newEmail, 'is_self_change' => $user->id === $requestedBy->id, ]) ->log('user.email_change_requested'); return $request; } /** * Verify and execute the email change. */ public function verifyChange(string $plainToken): EmailChangeRequest { $hashedToken = hash('sha256', $plainToken); $request = EmailChangeRequest::where('token', $hashedToken) ->where('status', EmailChangeStatus::PENDING) ->first(); if (! $request) { throw ValidationException::withMessages([ 'token' => ['Ongeldige of verlopen verificatielink.'], ]); } if ($request->isExpired()) { $request->update(['status' => EmailChangeStatus::EXPIRED]); throw ValidationException::withMessages([ 'token' => ['Deze verificatielink is verlopen. Vraag opnieuw een e-mailwijziging aan.'], ]); } // Final check: new email still not in use if (User::where('email', $request->new_email) ->where('id', '!=', $request->user_id)->exists()) { $request->update(['status' => EmailChangeStatus::CANCELLED]); throw ValidationException::withMessages([ 'new_email' => ['Dit e-mailadres is inmiddels in gebruik door een ander account.'], ]); } $user = $request->user; $oldEmail = $user->email; DB::transaction(function () use ($request, $user) { // Update user email $user->update(['email' => $request->new_email]); // Mark request as verified $request->update([ 'status' => EmailChangeStatus::VERIFIED, 'verified_at' => now(), ]); }); // Send confirmation to the OLD email address Mail::to($oldEmail)->send(new EmailChangedConfirmationMail( user: $user, oldEmail: $oldEmail, newEmail: $request->new_email, )); // Revoke all tokens (force re-login with new email) $user->tokens()->delete(); // Log linked person email context $persons = Person::where('user_id', $user->id)->get(); foreach ($persons as $person) { activity() ->causedBy($user) ->performedOn($person) ->withProperties([ 'old_user_email' => $oldEmail, 'new_user_email' => $request->new_email, 'person_email_unchanged' => $person->email, ]) ->log('person.linked_user_email_changed'); } activity() ->performedOn($user) ->withProperties([ 'old_email' => $oldEmail, 'new_email' => $request->new_email, ]) ->log('user.email_changed'); return $request; } }