diff --git a/config/routes.yaml b/config/routes.yaml
index 0c938b7..886f857 100644
--- a/config/routes.yaml
+++ b/config/routes.yaml
@@ -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
diff --git a/src/Command/AddInvitesCommand.php b/src/Command/AddInvitesCommand.php
index 1c30c14..c27a585 100644
--- a/src/Command/AddInvitesCommand.php
+++ b/src/Command/AddInvitesCommand.php
@@ -44,7 +44,7 @@ class AddInvitesCommand extends Command
$this->em->flush();
- $output->writeln(sprintf('%d invites added to \'%s\'.', $number, $user->getUsername()));
+ $output->writeln(sprintf('%d invites added to \'%s\'.', $number, $user->getUserIdentifier()));
return Command::SUCCESS;
}
diff --git a/src/Command/AddUserCommand.php b/src/Command/AddUserCommand.php
index 8f4da3c..eb438ac 100644
--- a/src/Command/AddUserCommand.php
+++ b/src/Command/AddUserCommand.php
@@ -74,7 +74,7 @@ class AddUserCommand extends Command
$this->em->flush();
- $output->writeln(sprintf('User \'%s\' registered, %d invites added.', $user->getUsername(), $invites));
+ $output->writeln(sprintf('User \'%s\' registered, %d invites added.', $user->getUserIdentifier(), $invites));
return Command::SUCCESS;
}
diff --git a/src/Controller/AccountController.php b/src/Controller/AccountController.php
index 5792131..be38502 100644
--- a/src/Controller/AccountController.php
+++ b/src/Controller/AccountController.php
@@ -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);
+ }
}
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index a02f478..fe3827a 100644
--- a/src/Controller/UserController.php
+++ b/src/Controller/UserController.php
@@ -5,13 +5,15 @@ namespace App\Controller;
use App\Entity\{Invite, PasswordResetToken};
use App\Repository\PasswordResetTokenRepository;
-use App\Form\{Data\PasswordResetRequestData, Data\PasswordResetData, PasswordResetRequestType, PasswordResetType, RegisterType, Data\RegisterData};
+use App\Form\Data\{PasswordResetRequestData, PasswordResetData, RegisterData};
+use App\Form\{PasswordResetRequestType, PasswordResetType, RegisterType};
use App\Repository\InviteRepository;
use App\User\{Exception\UserNotFoundException, InviteManager, PasswordResetManager, UserManager};
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\{Extension\Core\Type\SubmitType, FormError, FormInterface};
use Symfony\Component\HttpFoundation\{Request, Response};
+use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
class UserController extends AbstractController
{
@@ -23,8 +25,8 @@ class UserController extends AbstractController
InviteManager $inviteManager,
InviteRepository $inviteRepo
): Response {
- $formData = new RegisterData($code);
- $form = $this->createRegisterForm($formData, $code);
+ $data = new RegisterData($code);
+ $form = $this->createRegisterForm($data, $code);
/** @var Invite $invite */
$invite = $inviteRepo->findOneBy(['code' => $code, 'usedBy' => null]);
@@ -33,9 +35,9 @@ class UserController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$user = $userManager->createUserByInvite(
- $formData->username,
- $formData->password,
- $formData->email,
+ $data->username,
+ $data->password,
+ $data->email,
$invite
);
@@ -54,8 +56,8 @@ class UserController extends AbstractController
public function requestReset(Request $request, EntityManagerInterface $em, PasswordResetManager $manager): Response
{
- $formData = new PasswordResetRequestData();
- $form = $this->createResetRequestForm($formData);
+ $data = new PasswordResetRequestData();
+ $form = $this->createResetRequestForm($data);
$form->handleRequest($request);
@@ -63,7 +65,7 @@ class UserController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
try {
- $manager->sendResetLink($formData->email);
+ $manager->sendResetLink($data->email);
$message = 'Password reset link was sent';
} catch (UserNotFoundException $e) {
@@ -84,12 +86,12 @@ class UserController extends AbstractController
string $code,
Request $request,
EntityManagerInterface $em,
- UserManager $manager,
- PasswordResetTokenRepository $tokenRepository
+ PasswordHasherFactoryInterface $hasherFactory,
+ PasswordResetTokenRepository $tokenRepository,
): Response
{
- $formData = new PasswordResetData();
- $form = $this->createResetForm($formData, $code);
+ $data = new PasswordResetData();
+ $form = $this->createPasswordResetForm($data, $code);
/** @var PasswordResetToken $token */
$token = $tokenRepository->find($code);
@@ -98,15 +100,17 @@ class UserController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
if ($token && $token->isValid()) {
- $manager->changePassword($token->getUser(), $formData->password);
+ $user = $token->getUser();
+ $hasher = $hasherFactory->getPasswordHasher($user);
+ $user->changePassword($hasher, $data->password);
$em->remove($token);
$em->flush();
return $this->redirectToRoute('user_auth_login');
- } else {
- $form->addError(new FormError('Invalid token.'));
}
+
+ $form->addError(new FormError('Invalid token.'));
}
return $this->render('User/reset.html.twig', [
@@ -116,32 +120,28 @@ class UserController extends AbstractController
private function createResetRequestForm(PasswordResetRequestData $formData): FormInterface
{
- $form = $this->createForm(PasswordResetRequestType::class, $formData, [
- 'action' => $this->generateUrl('user_reset_request'),
- ]);
- $form->add('submit', SubmitType::class);
-
- return $form;
+ return $this
+ ->createForm(PasswordResetRequestType::class, $formData, [
+ 'action' => $this->generateUrl('user_reset_request'),
+ ])
+ ->add('submit', SubmitType::class);
}
- private function createResetForm(PasswordResetData $formData, string $code): FormInterface
+ private function createPasswordResetForm(PasswordResetData $data, string $code): FormInterface
{
- $form = $this->createForm(PasswordResetType::class, $formData, [
- 'action' => $this->generateUrl('user_reset', ['code' => $code]),
- ]);
- $form->add('submit', SubmitType::class);
-
- return $form;
+ return $this
+ ->createForm(PasswordResetType::class, $data, [
+ 'action' => $this->generateUrl('user_reset', ['code' => $code]),
+ ])
+ ->add('submit', SubmitType::class);
}
private function createRegisterForm(RegisterData $formData, string $code): FormInterface
{
- $form = $this->createForm(RegisterType::class, $formData, [
- 'action' => $this->generateUrl('user_register', ['code' => $code]),
- ]);
-
- $form->add('submit', SubmitType::class);
-
- return $form;
+ return $this
+ ->createForm(RegisterType::class, $formData, [
+ 'action' => $this->generateUrl('user_register', ['code' => $code]),
+ ])
+ ->add('submit', SubmitType::class);
}
}
\ No newline at end of file
diff --git a/src/Form/Data/PasswordChangeData.php b/src/Form/Data/PasswordChangeData.php
new file mode 100644
index 0000000..ff87186
--- /dev/null
+++ b/src/Form/Data/PasswordChangeData.php
@@ -0,0 +1,19 @@
+add('_username', TextType::class, ['mapped' => false, 'required' => true])
diff --git a/src/Form/PasswordChangeType.php b/src/Form/PasswordChangeType.php
new file mode 100644
index 0000000..36fe90c
--- /dev/null
+++ b/src/Form/PasswordChangeType.php
@@ -0,0 +1,38 @@
+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,
+ ]);
+ }
+}
diff --git a/src/Form/PasswordResetRequestType.php b/src/Form/PasswordResetRequestType.php
index a3adf34..156774b 100644
--- a/src/Form/PasswordResetRequestType.php
+++ b/src/Form/PasswordResetRequestType.php
@@ -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,
diff --git a/src/Form/PasswordResetType.php b/src/Form/PasswordResetType.php
index b955c6f..06d2690 100644
--- a/src/Form/PasswordResetType.php
+++ b/src/Form/PasswordResetType.php
@@ -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,
diff --git a/src/Form/RegisterType.php b/src/Form/RegisterType.php
index 5005d3e..8a99b1c 100644
--- a/src/Form/RegisterType.php
+++ b/src/Form/RegisterType.php
@@ -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,
diff --git a/src/User/UserManager.php b/src/User/UserManager.php
index 9fab5a5..a4099af 100644
--- a/src/User/UserManager.php
+++ b/src/User/UserManager.php
@@ -34,11 +34,6 @@ class UserManager
return $user;
}
- public function changePassword(User $user, string $rawPassword): void
- {
- $user->changePassword($this->hasherFactory->getPasswordHasher(User::class), $rawPassword);
- }
-
public function createUserByInvite(string $username, string $password, string $email, Invite $invite, array $roles = self::DEFAULT_ROLES): User
{
if (null !== $invite->getUsedBy()) {
diff --git a/templates/Account/password.html.twig b/templates/Account/password.html.twig
new file mode 100644
index 0000000..0b939a9
--- /dev/null
+++ b/templates/Account/password.html.twig
@@ -0,0 +1,5 @@
+{% extends 'base.html.twig' %}
+
+{% block content %}
+{{ form(form) }}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/Account/profile.html.twig b/templates/Account/profile.html.twig
index 34a0c6d..f564296 100644
--- a/templates/Account/profile.html.twig
+++ b/templates/Account/profile.html.twig
@@ -10,7 +10,7 @@
Username |
- {{ user.username }} |
+ {{ user.userIdentifier }} |
|
@@ -20,5 +20,12 @@
+
+ Password |
+ ******** |
+
+ Change
+ |
+
\ No newline at end of file