#17. Implementing password change.
This commit is contained in:
parent
86e6a5870e
commit
f68d120ca5
|
@ -53,6 +53,12 @@ user_account:
|
|||
requirements:
|
||||
method: GET
|
||||
|
||||
user_account_password_change:
|
||||
path: /account/password
|
||||
controller: App\Controller\AccountController::changePassword
|
||||
requirements:
|
||||
method: POST
|
||||
|
||||
user_account_token_create:
|
||||
path: /profile/api/token/create
|
||||
controller: App\Controller\AccountController::addApiToken
|
||||
|
|
|
@ -5,14 +5,21 @@ namespace App\Controller;
|
|||
|
||||
use App\Entity\{ApiToken, User};
|
||||
use App\Repository\{ApiTokenRepository, InviteRepository};
|
||||
use App\Form\Data\PasswordChangeData;
|
||||
use App\Form\PasswordChangeType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\{Request, Response};
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
|
||||
class AccountController extends AbstractController
|
||||
{
|
||||
public function account(InviteRepository $inviteRepo, ApiTokenRepository $apiTokenRepo): Response
|
||||
{
|
||||
public function account(
|
||||
InviteRepository $inviteRepo,
|
||||
ApiTokenRepository $apiTokenRepo
|
||||
): Response {
|
||||
/** @var User $user */
|
||||
if (null === $user = $this->getUser()) {
|
||||
throw $this->createAccessDeniedException('User not found exception');
|
||||
|
@ -26,6 +33,32 @@ class AccountController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
public function changePassword(
|
||||
Request $request,
|
||||
EntityManagerInterface $em,
|
||||
PasswordHasherFactoryInterface $hasherFactory,
|
||||
): Response {
|
||||
$data = new PasswordChangeData();
|
||||
$form = $this->createChangePasswordForm($data);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$hasher = $hasherFactory->getPasswordHasher($user);
|
||||
|
||||
$user->changePassword($hasher, $data->newPassword);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'Password changed.');
|
||||
|
||||
return $this->redirectToRoute('user_account');
|
||||
}
|
||||
|
||||
return $this->renderForm('Account/password.html.twig', [
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
public function addApiToken(EntityManagerInterface $em): Response
|
||||
{
|
||||
/** @var User|null $user */
|
||||
|
@ -57,4 +90,13 @@ class AccountController extends AbstractController
|
|||
|
||||
return $this->redirectToRoute('user_account');
|
||||
}
|
||||
|
||||
private function createChangePasswordForm(PasswordChangeData $data): FormInterface
|
||||
{
|
||||
return $this
|
||||
->createForm(PasswordChangeType::class, $data, [
|
||||
'action' => $this->generateUrl('user_account_password_change'),
|
||||
])
|
||||
->add('submit', SubmitType::class);
|
||||
}
|
||||
}
|
||||
|
|
19
src/Form/Data/PasswordChangeData.php
Normal file
19
src/Form/Data/PasswordChangeData.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Data;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert;
|
||||
|
||||
class PasswordChangeData
|
||||
{
|
||||
#[Assert\NotBlank]
|
||||
#[SecurityAssert\UserPassword(message: 'Wrong password')]
|
||||
public string $currentPassword;
|
||||
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(min: 8, max: 4096)]
|
||||
#[Assert\NotCompromisedPassword(skipOnError: true)]
|
||||
public string $newPassword;
|
||||
}
|
|
@ -9,7 +9,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
|
||||
class LoginType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('_username', TextType::class, ['mapped' => false, 'required' => true])
|
||||
|
|
38
src/Form/PasswordChangeType.php
Normal file
38
src/Form/PasswordChangeType.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Data\PasswordChangeData;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\{PasswordType, RepeatedType};
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PasswordChangeType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('currentPassword', PasswordType::class, [
|
||||
'label' => 'Current password',
|
||||
'invalid_message' => 'Wrong password.',
|
||||
'required' => true,
|
||||
])
|
||||
->add('newPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'invalid_message' => 'The password fields must match.',
|
||||
'required' => true,
|
||||
'first_options' => ['label' => 'New password'],
|
||||
'second_options' => ['label' => 'Repeat'],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordChangeData::class,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
|
||||
class PasswordResetRequestType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, ['required' => true])
|
||||
|
@ -18,7 +18,7 @@ class PasswordResetRequestType extends AbstractType
|
|||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordResetRequestData::class,
|
||||
|
|
|
@ -11,7 +11,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
|
||||
class PasswordResetType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('password', RepeatedType::class, [
|
||||
|
@ -24,7 +24,7 @@ class PasswordResetType extends AbstractType
|
|||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordResetData::class,
|
||||
|
|
|
@ -11,7 +11,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
|
||||
class RegisterType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('username', TextType::class, ['required' => true])
|
||||
|
@ -21,7 +21,7 @@ class RegisterType extends AbstractType
|
|||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => RegisterData::class,
|
||||
|
|
5
templates/Account/password.html.twig
Normal file
5
templates/Account/password.html.twig
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{{ form(form) }}
|
||||
{% endblock %}
|
|
@ -20,5 +20,12 @@
|
|||
<button type="button" class="btn disabled" title="Not implemented">Change</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Password</th>
|
||||
<td>********</td>
|
||||
<td>
|
||||
<a role="button" class="btn btn-primary" href="{{ path('user_account_password_change') }}">Change</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
Loading…
Reference in a new issue