Symfony 4 смените пароль по имени пользователя - адрес электронной почты не может быть нулевым - PullRequest
0 голосов
/ 25 мая 2018

Введение

Я пытался выяснить, как создать форму пароля для сброса, которая определяется значением имени пользователя.

Ошибка

Path        Message                           Invalid value     Violation
data.email  This value should not be blank.   null 



ConstraintViolation {#945 ▼
  -message: "This value should not be blank."
  -messageTemplate: "This value should not be blank."
  -parameters: [▶]
  -plural: null
  -root: Form {#620 ▶}
  -propertyPath: "data.email"
  -invalidValue: null
  -constraint: NotBlank {#477 …}
  -code: "c1051bb4-d103-4f74-8988-acbcafc7fdc3"
  -cause: null
}

Что ожидается

Обновите мой объект пользователя новым паролем.

Мой код

ForgotController.php

Я знаю, что это, вероятно, неправильный способ получить пароль, но поиск в форме забытого пароля в Symfony 4 вызывает сообщения symfony2.4, которые не актуальнык моей версии

    <?php
        namespace App\Controller\User;

        use App\Entity\User;
        use App\Form\User\ChangePasswordType;
        use App\Repository\UserRepository;
        use Symfony\Bundle\FrameworkBundle\Controller\Controller;
        use Symfony\Component\HttpFoundation\Request;
        use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

        class ForgotController extends Controller
        {
            public function forgot(Request $request, UserPasswordEncoderInterface $encoder)
            {
                $entityManager = $this->getDoctrine()->getManager();

                $changePassword = $request->request->get('change_password');

                $username = $changePassword['username'];
                $password = $changePassword['plainPassword']['first'];

                $user       = $entityManager->getRepository(User::class)->findBy(['username' => $username]);
                $userEntity = new User();

                if (!$user) {
                    $this->addFlash('danger', 'User not found for '. $username);
                }

                $form = $this->createForm(ChangePasswordType::class, $userEntity);
                $form->handleRequest($request);

                if ($form->isSubmitted() && $form->isValid()) {
                    try {
                        $pass = $encoder->encodePassword($userEntity, $password);

                        $userEntity->setPassword($pass);
                        $entityManager->flush();

                        $this->addFlash('success', 'Password Changed!');
                    } catch (Exception $e) {
                        $this->addFlash('danger', 'Something went skew-if. Please try again.');
                    }

                    return $this->redirectToRoute('login');
                }

                return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
            }
        }

ChangePasswordType.php

<?php
    namespace App\Form\User;

    use App\Entity\User;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\PasswordType;
    use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
    use Symfony\Component\Form\Extension\Core\Type\TextType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;

    class ChangePasswordType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('username', TextType::class)
                ->add('plainPassword', RepeatedType::class, array(
                'type' => PasswordType::class,
                'first_options' => array('label' => 'New Password'),
                'second_options' => array('label' => 'Repeat New Password')
            ));
        }

        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => User::class
            ));
        }
    }

Forgot.html.twig

{% include 'builder/header.html.twig' %}

<div class="user-container" id="user-content">
    {% block body %}
        {% include 'builder/notices.html.twig' %}

        <div class="user-container">
            <i class="fas fa-user-edit fa-5x"></i>
        </div>

        <hr />

        {{ form_start(form) }}
            {{ form_row(form.username, { 'attr': {'class': 'form-control'} }) }}
            {{ form_row(form.plainPassword.first, { 'attr': {'class': 'form-control'} }) }}
            {{ form_row(form.plainPassword.second, { 'attr': {'class': 'form-control'} }) }}

            <div class="register-btn-container">
                <button class="btn btn-danger" id="return-to-dash-btn" type="button">Cancel!</button>
                <button class="btn btn-primary" type="submit">Update!</button>
            </div>
        {{ form_end(form) }}
    {% endblock %}
</div>

{% include 'builder/footer.html.twig' %}

Я не уверен, почему электронная почта даже упоминается, если она не пытается вставить нового пользователя в базу данных, но она не должна пытаться сделать это на основе моего контроллера?Как я могу добавить форму забытого пароля, которая определяется по имени пользователя?

Ответы [ 2 ]

0 голосов
/ 25 мая 2018

Реализуйте что-нибудь по принципу ниже - я оставил кусочки, такие как шаблоны и маршрутизация.Это просто, чтобы помочь вам в этом.

Форма 1: ForgottenUserType - использование вводит только имя пользователя / адрес электронной почты и отправляет

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add("username", null, array(
        "label" => "Email",
        "attr" => array(
            "class" => "form-control",
            "id" => "basic-url",
            "placeholder" => "Email address for your account"
        ),
        "constraints" => array(
            new Email(array("message" => "Invalid Email"))
        )
    ));
}

Форма 2: ChangePasswordFormType - пользователь вводит и повторяет новый пароль.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    parent::buildForm($builder, $options);
    $builder
        ->add('plainPassword', RepeatedType::class, array(
            'type'              => PasswordType::class,
            'required'          => false,
            'first_options'     => array('label' => 'New password'),
            'second_options'    => array('label' => 'Confirm new password'),
            'invalid_message' => 'The password fields must match.',
        ))
        ;
}

Контроллер: ResetPasswordController - обрабатывает пользовательский запрос поиска формы 1 и запрос сброса пароля для формы 2:

<?php
namespace App\Controller\User;

use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class ResetPasswordController extends Controller
{
    /**
     * Action for when a user has forgotten their password, to request ForgottenUser form
     *
     * @param Request $request
     */
    public function requestAction(Request $request)
    {
        $tmpUser = new User();
        $entityManager = $this->getDoctrine()->getManager();

        $form = $this->createForm(ForgottenUserType::class, $tmpUser);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $user = $entityManager->getRepository(User::class)->findBy(['username' => $tmpUser->getUsername()]);

            if ($user) {

                //check and set token
                if (null === $user->getConfirmationToken()) {
                    /** @var $tokenGenerator TokenGeneratorInterface */
                    $token = md5(uniqid($user->getUsername(), true)); //some unique token (you can create a nicer token generator in standalone class with a service)
                    $user->setConfirmationToken($token);
                    $user->setPasswordRequestedAt(new \DateTime());
                    $em->persist($user);
                    $em->flush();)

                    $this->addFlash('Info', 'If user is found, you will receive an email with further instructions.');

                    //send email using swiftmailer & include url with token
                }


            } else {
                //return to requestAction.
            }
        }

        //request template contains the ForgottenUserType form
        return $this->render(":path/to/template:request.html.twig", array(
            "forgotten_form" => $form->createView()
        ));

    }

    /**
     * Reset user password.
     *
     * @param Request $request
     * @param $token
     */
    public function resetAction(Request $request, $token)
    {
        $entityManager = $this->getDoctrine()->getManager();
        $user = $entityManager->getRepository(User::class)->findBy(['confirmationToken' => $token]); 

        if (null === $user) {                        
            return new RedirectResponse($this->generateUrl('resetting_request')); //to requestAction above. / create route
        }        

        $form = $this->createForm(ChangePasswordFormType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $user->SetConfirmationToken(null);
            $user->setPasswordRequestedAt(null);
            $entityManager->persist($user);
            $entityManager->flush()

            $this->addFlash("success", "Your password has been reset, log in now.");
            $url = $this->generateUrl('app.login'); //route to login page
            $response = new RedirectResponse($url);
            return $response;            
        }

        //reset template contains the ChangePasswordFormType form
        return $this->render(':path/to/forgottenpasswordtemplate:reset.html.twig', array(
            'token' => $token,
            'form' => $form->createView(),
        ));

    }
}
0 голосов
/ 25 мая 2018

Поскольку вашей форме смены пароля требуется только два поля, мы будем использовать массив вместо пользовательского объекта.Нужна небольшая настройка ChangePasswordType:

    // ChangePasswordType
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            //'data_class' => User::class
        ));
    }

Вот рабочее забытое действие:

    public function forgot(
        Request $request, 
        UserPasswordEncoderInterface $encoder, 
        UserRepository $userRepository)
    {

        $userInfo = ['username' => null, 'plainPassword' => null];

        $form = $this->createForm(ChangePasswordType::class, $userInfo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $userInfo = $form->getData();
            $username = $userInfo['username'];
            $plainPassword = $userInfo['plainPassword'];

            $user = $userRepository->findOneBy(['username' => $username]);
            if ($user === null) {
                $this->addFlash('danger', 'Invalid username');
                return $this->redirectToRoute('forgot');
            }
            $password = $encoder->encodePassword($user, $plainPassword);

            $user->setPassword($password);
            $userRepository->flush();

            return $this->redirectToRoute('login');
        }

        return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
    }

Введен UserRepository, который избавляет от всей чепухи доктрины.Здесь есть одна оговорка, к которой я вернусь.

Мы создаем массив userInfo и позволяем обработке формы делать свое дело.Мы действительно не хотим возиться с получением атрибутов непосредственно из объекта запроса, если нам не нужно.

Тогда мы получим нашу актуальную пользовательскую сущность для обновления.Обратите внимание на использование findOneBy вместо findBy.Мы проверяем, чтобы убедиться в правильности имени пользователя.Если вы действительно хотите получить фантазию, то можете добавить в форму ограничение проверки, чтобы автоматически выполнить эту проверку.

Я избавился от всего, что касается try / catch.Это просто загромождает ваш код.К этому моменту, если выдается исключение, оно становится действительно исключительным и может обрабатываться обработчиками исключений по умолчанию.

Вы правильно разбираетесь в кодировщике паролей.

И тогда вместо $ entityManager-> flush () Я использовал $ userRepository-> flush ();В хранилище нет встроенного метода очистки, поэтому вам нужно добавить один:

// UserRepository
public function flush()
{
    $this->_em->flush();
}

Мне лично нравится иметь дело только с хранилищами, а не с менеджером сущностей.Но если хотите, вы можете просто вернуться и ввести менеджер вместо репозитория.Ваш звонок.

И, как уже упоминалось в комментариях, вы хотите добавить некоторую защиту, чтобы пользователи не могли изменять пароли других пользователей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...