fix: prevent empty phone numbers from being accepted when updating user information (#774)

This commit is contained in:
Maxiloud 2025-10-31 16:35:20 +01:00 committed by Paul Andrieux
parent 92e4466f4f
commit 9d98b8ffc6
4 changed files with 85 additions and 36 deletions

View file

@ -21,12 +21,15 @@ use App\MessageBus\CommandBus;
use App\MessageBus\QueryBus;
use App\MessageHandler\Command\Security\AccountCreateStep1CommandHandler;
use App\Repository\ConfigurationRepository;
use libphonenumber\PhoneNumber;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @see AccountCreateActionStep1Test
@ -44,6 +47,7 @@ final class AccountCreateController extends AbstractController
private readonly CommandBus $commandBus,
private readonly Security $security,
private readonly ConfigurationRepository $configurationRepository,
private readonly TranslatorInterface $translator,
) {
$this->i18nPrefix = $this->getI18nPrefix();
}
@ -103,40 +107,55 @@ final class AccountCreateController extends AbstractController
$configuration = $this->configurationRepository->getInstanceConfigurationOrCreate();
// nominal case: user found and token not expired
$form = $this->createForm(AccountCreateStep2FormType::class, $user->setStep2Defaults())->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var User $user */
$user = $form->getData();
$user->setType(UserType::USER);
$this->commandBus->dispatch(new AccountCreateStep2Command($user));
$this->security->login($user); // auto-log the user
if ($form->isSubmitted()) {
$phone = $form->get('phone')->getData() ?? '';
// If user has pending invitations then redirect them to the first group
// found without doing the confirmation stuff, it must be done on the
// page group.
$group = $user->getMyGroupsAsInvited()->first();
if ($group !== false) {
// If platform needs payment, redirect to payment
if ($configuration->getPaidMembership()) {
$successMessage = $this->i18nPrefix.'.step2.with_invitation.global_paid_membership.flash.success';
if (!$phone instanceof PhoneNumber || $phone->getNationalNumber() === '') {
$form->get('phone')->addError(
new FormError($this->translator->trans('account_create.phone.empty.error', [], 'validators'))
);
}
if ($phone instanceof PhoneNumber && \strlen($phone->getNationalNumber() ?? '') < 8) {
$form->get('phone')->addError(
new FormError($this->translator->trans('account_create.phone.short.error', [], 'validators'))
);
}
if ($form->isValid()) {
/** @var User $user */
$user = $form->getData();
$user->setType(UserType::USER);
$this->commandBus->dispatch(new AccountCreateStep2Command($user));
$this->security->login($user); // auto-log the user
// If user has pending invitations then redirect them to the first group
// found without doing the confirmation stuff, it must be done on the
// page group.
$group = $user->getMyGroupsAsInvited()->first();
if ($group !== false) {
// If platform needs payment, redirect to payment
if ($configuration->getPaidMembership()) {
$successMessage = $this->i18nPrefix.'.step2.with_invitation.global_paid_membership.flash.success';
$this->addFlashSuccess($successMessage);
return $this->redirectToRoute('redirect_to_payment');
}
$successMessage = $this->i18nPrefix.'.step2.with_invitation.flash.success';
$this->addFlashSuccess($successMessage);
return $this->redirectToRoute('redirect_to_payment');
return $this->redirectToRoute('app_group_show_logged', $group->getRoutingParameters());
}
$successMessage = $this->i18nPrefix.'.step2.with_invitation.flash.success';
if ($configuration->getPaidMembership()) {
$successMessage = $this->i18nPrefix.'.step2.global_paid_membership.flash.success';
} else {
$successMessage = $this->i18nPrefix.'.step2.flash.success';
}
// otherwise go to the address form
$this->addFlashSuccess($successMessage);
return $this->redirectToRoute('app_group_show_logged', $group->getRoutingParameters());
return $this->redirectToRoute(MyAccountAction::ROUTE);
}
if ($configuration->getPaidMembership()) {
$successMessage = $this->i18nPrefix.'.step2.global_paid_membership.flash.success';
} else {
$successMessage = $this->i18nPrefix.'.step2.flash.success';
}
// otherwise go to the address form
$this->addFlashSuccess($successMessage);
return $this->redirectToRoute(MyAccountAction::ROUTE);
}
return $this->render('pages/register/step2.html.twig', compact('form', 'user'));

View file

@ -13,7 +13,9 @@ use App\Form\Type\User\EditProfileFormType;
use App\Repository\UserRepository;
use App\Tests\Functional\Controller\User\Account\EditProfileActionTest;
use Doctrine\ORM\EntityManagerInterface;
use libphonenumber\PhoneNumber;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@ -21,6 +23,7 @@ use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @see EditProfileActionTest
@ -34,6 +37,7 @@ final class EditProfileAction extends AbstractController
private readonly UserRepository $userRepository,
private readonly UserManager $userManager,
private readonly EntityManagerInterface $entityManager,
private readonly TranslatorInterface $translator,
) {
}
@ -45,14 +49,30 @@ final class EditProfileAction extends AbstractController
public function __invoke(Request $request, #[CurrentUser] User $user): Response
{
$form = $this->createForm(EditProfileFormType::class, $user)->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var UploadedFile|null $avatar */
$avatar = $form->get('avatar')->getData();
$this->userManager->upload($avatar, $user);
$this->userRepository->save($user, true);
$this->addFlashSuccess($this->getI18nPrefix().'.flash.success');
if ($form->isSubmitted()) {
$phone = $form->get('phone')->getData() ?? '';
return $this->redirectToRoute(MyAccountAction::ROUTE);
if (!$phone instanceof PhoneNumber || $phone->getNationalNumber() === '') {
$form->get('phone')->addError(
new FormError($this->translator->trans('account_create.phone.empty.error', [], 'validators'))
);
}
if ($phone instanceof PhoneNumber && \strlen($phone->getNationalNumber() ?? '') < 8) {
$form->get('phone')->addError(
new FormError($this->translator->trans('account_create.phone.short.error', [], 'validators'))
);
}
if ($form->isValid()) {
/** @var UploadedFile|null $avatar */
$avatar = $form->get('avatar')->getData();
$this->userManager->upload($avatar, $user);
$this->userRepository->save($user, true);
$this->addFlashSuccess($this->getI18nPrefix().'.flash.success');
return $this->redirectToRoute(MyAccountAction::ROUTE);
}
}
// In case of error, we must reload the original firstname (to display it in navbar)

View file

@ -44,7 +44,7 @@ final class EditProfileActionTest extends WebTestCase
$form->getName().'[category]' => TestReference::CATEGORY_OBJECT_1,
$form->getName().'[description]' => 'description test',
$form->getName().'[phone][country]' => 'FR',
$form->getName().'[phone][number]' => '',
$form->getName().'[phone][number]' => '634563424',
$form->getName().'[smsNotifications]' => false,
]);
@ -53,7 +53,7 @@ final class EditProfileActionTest extends WebTestCase
/** @var User $editedUser */
$editedUser = $repo->find(TestReference::USER_16);
self::assertNull($editedUser->getPhoneNumber());
self::assertNotNull($editedUser->getPhoneNumber());
self::assertResponseRedirects();
$client->followRedirect();

View file

@ -26,6 +26,16 @@
<target>Le nom du lieu est obligatoire</target>
</trans-unit>
<trans-unit id="tY0WDru" resname="account_create.phone.empty.error">
<source>account_create.phone.empty.error</source>
<target>Le numéro de téléphone est obligatoire</target>
</trans-unit>
<trans-unit id="tY0WDzu" resname="account_create.phone.short.error">
<source>account_create.phone.short.error</source>
<target>Veuillez indiquer un numéro de téléphone valide</target>
</trans-unit>
</body>
</file>
</xliff>