Add profile page for changing password and email
New /profile route (nav link between Admin and Logout) with two independent forms, both requiring the current password before applying a change: - Change password: standard current/new/repeat flow, same password strength rules as registration. - Change email: re-checks the new address isn't already taken (avoids a 500 from the unique constraint), then marks the account unverified and re-sends the confirmation email via the existing EmailVerifier/verify-email flow, same as registration. The current session stays logged in, but the user needs to verify the new address before their next login (UserChecker already blocks unverified accounts).
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tech\Controller;
|
||||
|
||||
use App\Tech\Entity\User;
|
||||
use App\Tech\Form\ProfileChangeEmailFormType;
|
||||
use App\Tech\Form\ProfileChangePasswordFormType;
|
||||
use App\Tech\Repository\UserRepository;
|
||||
use App\Tech\Service\EmailVerifier;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
final class ProfileController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly EmailVerifier $emailVerifier,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/profile', name: 'app_profile', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$passwordForm = $this->createForm(ProfileChangePasswordFormType::class);
|
||||
$passwordForm->handleRequest($request);
|
||||
|
||||
if ($passwordForm->isSubmitted() && $passwordForm->isValid()) {
|
||||
$currentPassword = (string) $passwordForm->get('currentPassword')->getData();
|
||||
|
||||
if (!$this->passwordHasher->isPasswordValid($user, $currentPassword)) {
|
||||
$passwordForm->get('currentPassword')->addError(new FormError('Incorrect password.'));
|
||||
} else {
|
||||
$plainPassword = (string) $passwordForm->get('plainPassword')->getData();
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, $plainPassword));
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Your password has been updated.');
|
||||
|
||||
return $this->redirectToRoute('app_profile');
|
||||
}
|
||||
}
|
||||
|
||||
$emailForm = $this->createForm(ProfileChangeEmailFormType::class, ['email' => $user->getEmail()]);
|
||||
$emailForm->handleRequest($request);
|
||||
|
||||
if ($emailForm->isSubmitted() && $emailForm->isValid()) {
|
||||
$currentPassword = (string) $emailForm->get('currentPassword')->getData();
|
||||
|
||||
if (!$this->passwordHasher->isPasswordValid($user, $currentPassword)) {
|
||||
$emailForm->get('currentPassword')->addError(new FormError('Incorrect password.'));
|
||||
} else {
|
||||
$newEmail = (string) $emailForm->get('email')->getData();
|
||||
|
||||
if ($newEmail === $user->getEmail()) {
|
||||
$this->addFlash('info', 'That is already your email address.');
|
||||
return $this->redirectToRoute('app_profile');
|
||||
}
|
||||
|
||||
$existing = $this->userRepository->findOneBy(['email' => $newEmail]);
|
||||
if ($existing) {
|
||||
$emailForm->get('email')->addError(new FormError('This email address is already in use.'));
|
||||
} else {
|
||||
$user->setEmail($newEmail);
|
||||
$user->setIsVerified(false);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
|
||||
(new TemplatedEmail())
|
||||
->from($this->getParameter('mailer_from'))
|
||||
->to($newEmail)
|
||||
->subject('Please confirm your new email address')
|
||||
->htmlTemplate('tech/registration/confirmation_email.html.twig')
|
||||
);
|
||||
|
||||
$this->addFlash('success', 'Your email address has been updated. You must verify it (check your inbox) before you can log in again.');
|
||||
|
||||
return $this->redirectToRoute('app_profile');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('tech/profile/index.html.twig', [
|
||||
'user' => $user,
|
||||
'passwordForm' => $passwordForm,
|
||||
'emailForm' => $emailForm,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user