diff --git a/src/Action/LoginAction.php b/src/Action/LoginAction.php index 9a67d2d3..0705b84e 100644 --- a/src/Action/LoginAction.php +++ b/src/Action/LoginAction.php @@ -14,13 +14,15 @@ namespace Nucleos\UserBundle\Action; use Nucleos\UserBundle\Event\GetResponseLoginEvent; +use Nucleos\UserBundle\Form\Type\LoginFormType; use Nucleos\UserBundle\NucleosUserEvents; +use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Twig\Environment; @@ -37,18 +39,25 @@ final class LoginAction private $eventDispatcher; /** - * @var CsrfTokenManagerInterface|null + * @var FormFactoryInterface */ - private $tokenManager; + private $formFactory; + + /** + * @var RouterInterface + */ + private $router; public function __construct( Environment $twig, EventDispatcherInterface $eventDispatcher, - CsrfTokenManagerInterface $tokenManager = null + FormFactoryInterface $formFactory, + RouterInterface $router ) { $this->twig = $twig; $this->eventDispatcher = $eventDispatcher; - $this->tokenManager = $tokenManager; + $this->formFactory = $formFactory; + $this->router = $router; } /** @@ -82,21 +91,23 @@ public function __invoke(Request $request): Response $error = null; // The value does not come from the security component. } + $form = $this->formFactory->create(LoginFormType::class, null, [ + 'action' => $this->router->generate('nucleos_user_security_check'), + 'method' => 'POST', + ]); + // last username entered by the user $lastUsername = (null === $session) ? '' : $session->get($lastUsernameKey); return new Response($this->twig->render('@NucleosUser/Security/login.html.twig', [ 'last_username' => $lastUsername, - 'error' => $error, - 'csrf_token' => $this->getCsrfToken(), + 'form' => $form->createView(), + // TODO: Remove this fields with the next major release + 'error' => null, + 'csrf_token' => '', ])); } - private function getCsrfToken(): ?string - { - return null !== $this->tokenManager ? $this->tokenManager->getToken('authenticate')->getValue() : null; - } - private function getSession(Request $request): ?SessionInterface { return $request->hasSession() ? $request->getSession() : null; diff --git a/src/Action/RequestResetAction.php b/src/Action/RequestResetAction.php index 0e100c2c..9b4da8d7 100644 --- a/src/Action/RequestResetAction.php +++ b/src/Action/RequestResetAction.php @@ -11,7 +11,10 @@ namespace Nucleos\UserBundle\Action; +use Nucleos\UserBundle\Form\Type\RequestPasswordFormType; +use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\RouterInterface; use Twig\Environment; final class RequestResetAction @@ -22,15 +25,31 @@ final class RequestResetAction private $twig; /** - * RequestAction constructor. + * @var FormFactoryInterface */ - public function __construct(Environment $twig) + private $formFactory; + + /** + * @var RouterInterface + */ + private $router; + + public function __construct(Environment $twig, FormFactoryInterface $formFactory, RouterInterface $router) { - $this->twig = $twig; + $this->twig = $twig; + $this->formFactory = $formFactory; + $this->router = $router; } public function __invoke(): Response { - return new Response($this->twig->render('@NucleosUser/Resetting/request.html.twig')); + $form = $this->formFactory->create(RequestPasswordFormType::class, null, [ + 'action' => $this->router->generate('nucleos_user_resetting_send_email'), + 'method' => 'POST', + ]); + + return new Response($this->twig->render('@NucleosUser/Resetting/request.html.twig', [ + 'form' => $form->createView(), + ])); } } diff --git a/src/Form/Type/LoginFormType.php b/src/Form/Type/LoginFormType.php new file mode 100644 index 00000000..eed749d3 --- /dev/null +++ b/src/Form/Type/LoginFormType.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nucleos\UserBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; + +final class LoginFormType extends AbstractType +{ + /** + * @var RequestStack + */ + private $requestStack; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('_username', TextType::class, [ + 'label' => 'security.login.username', + 'attr' => [ + 'autocomplete' => 'username', + ], + ]) + ->add('_password', PasswordType::class, [ + 'label' => 'security.login.password', + 'attr' => [ + 'autocomplete' => 'password', + ], + ]) + ->add('_remember_me', CheckboxType::class, [ + 'label' => 'security.login.remember_me', + 'required' => false, + 'value' => 'on', + ]) + ->add('_target_path', HiddenType::class) + ->add('save', SubmitType::class, [ + 'label' => 'security.login.submit', + ]) + ; + + $request = $this->requestStack->getCurrentRequest(); + + $builder->addEventListener(FormEvents::PRE_SET_DATA, static function (FormEvent $event) use ($request): void { + if (null === $request) { + return; + } + + $error = null; + + if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + } else { + $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + } + + if (null !== $error) { + $event->getForm()->addError(new FormError($error->getMessage())); + } + + $event->setData(array_replace((array) $event->getData(), [ + 'username' => $request->getSession()->get(Security::LAST_USERNAME), + ])); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'translation_domain' => 'NucleosUserBundle', + ]); + } + + public function getBlockPrefix(): string + { + return ''; + } +} diff --git a/src/Form/Type/RequestPasswordFormType.php b/src/Form/Type/RequestPasswordFormType.php new file mode 100644 index 00000000..503e1e31 --- /dev/null +++ b/src/Form/Type/RequestPasswordFormType.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nucleos\UserBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +final class RequestPasswordFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('username', TextType::class, [ + 'label' => 'resetting.request.username', + 'attr' => [ + 'autocomplete' => 'username', + ], + ]) + ->add('save', SubmitType::class, [ + 'label' => 'resetting.request.submit', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'translation_domain' => 'NucleosUserBundle', + ]); + } + + public function getBlockPrefix(): string + { + return ''; + } +} diff --git a/src/Resources/config/resetting.php b/src/Resources/config/resetting.php index 290a8b42..67401089 100644 --- a/src/Resources/config/resetting.php +++ b/src/Resources/config/resetting.php @@ -17,6 +17,7 @@ use Nucleos\UserBundle\Action\SendEmailAction; use Nucleos\UserBundle\EventListener\ResettingListener; use Nucleos\UserBundle\Form\Model\Resetting; +use Nucleos\UserBundle\Form\Type\RequestPasswordFormType; use Nucleos\UserBundle\Form\Type\ResettingFormType; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; @@ -30,6 +31,9 @@ Resetting::class, ]) + ->set(RequestPasswordFormType::class) + ->tag('form.type') + ->set(ResettingListener::class) ->tag('kernel.event_subscriber') ->args([ @@ -41,6 +45,8 @@ ->public() ->args([ new Reference('twig'), + new Reference('form.factory'), + new Reference('router'), ]) ->set(ResetAction::class) diff --git a/src/Resources/config/security.php b/src/Resources/config/security.php index d0ef8cee..32811f30 100644 --- a/src/Resources/config/security.php +++ b/src/Resources/config/security.php @@ -16,12 +16,12 @@ use Nucleos\UserBundle\Action\LoginAction; use Nucleos\UserBundle\Action\LogoutAction; use Nucleos\UserBundle\EventListener\LastLoginListener; +use Nucleos\UserBundle\Form\Type\LoginFormType; use Nucleos\UserBundle\Security\EmailProvider; use Nucleos\UserBundle\Security\EmailUserProvider; use Nucleos\UserBundle\Security\LoginManager; use Nucleos\UserBundle\Security\LoginManagerInterface; use Nucleos\UserBundle\Security\UserProvider; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; return static function (ContainerConfigurator $container): void { @@ -59,12 +59,19 @@ new Reference('nucleos_user.user_manager'), ]) + ->set(LoginFormType::class) + ->tag('form.type') + ->args([ + new Reference('request_stack'), + ]) + ->set(LoginAction::class) ->public() ->args([ new Reference('twig'), new Reference('event_dispatcher'), - new Reference('security.csrf.token_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE), + new Reference('form.factory'), + new Reference('router'), ]) ->set(LogoutAction::class) diff --git a/src/Resources/views/Resetting/request_content.html.twig b/src/Resources/views/Resetting/request_content.html.twig index d8563e2a..4048b7b8 100644 --- a/src/Resources/views/Resetting/request_content.html.twig +++ b/src/Resources/views/Resetting/request_content.html.twig @@ -1,14 +1,7 @@ {% trans_default_domain 'NucleosUserBundle' %} -
-
- - -
+{{ form_errors(form) }} -
- -
-
+{{ form_start(form) }} + {{ form_widget(form) }} +{{ form_end(form) }} diff --git a/src/Resources/views/Security/login_content.html.twig b/src/Resources/views/Security/login_content.html.twig index 718985ee..ca207ee0 100644 --- a/src/Resources/views/Security/login_content.html.twig +++ b/src/Resources/views/Security/login_content.html.twig @@ -1,43 +1,17 @@ {% trans_default_domain 'NucleosUserBundle' %} -{% if error %} -
{{ error.messageKey|trans(error.messageData, 'security') }}
-{% endif %} +{{ form_errors(form) }} -
+{{ form_start(form) }}
{{ 'security.login.title'|trans }} -
- - -
- -
- - -
- -
- - -
- - - {% if csrf_token %} - - {% endif %} + {{ form_widget(form) }}
- - {{ 'security.forgotten_password'|trans }}
-
+{{ form_end(form) }}