refs #450. Reset password from user area
parent
9aa0514720
commit
1f53f186fa
|
@ -8,7 +8,6 @@ resources:
|
|||
groups: ['default', 'user:read']
|
||||
denormalization_context:
|
||||
groups: ['user:write']
|
||||
|
||||
operations:
|
||||
ApiPlatform\Metadata\GetCollection:
|
||||
provider: App\State\Provider\UserProvider
|
||||
|
@ -26,6 +25,15 @@ resources:
|
|||
validationContext:
|
||||
groups: [ 'default', 'user:post' ]
|
||||
ApiPlatform\Metadata\Delete: ~
|
||||
reset_password:
|
||||
provider: App\State\Provider\UserProvider
|
||||
class: ApiPlatform\Metadata\Put
|
||||
method: PUT
|
||||
input: App\Dto\Input\UserInput
|
||||
uriTemplate: /users/{uuid}/reset-password
|
||||
controller: App\Controller\ResetPasswordAction
|
||||
validationContext:
|
||||
groups: [ 'user:reset-password' ]
|
||||
|
||||
properties:
|
||||
App\Entity\User:
|
||||
|
|
|
@ -9,6 +9,7 @@ api_platform:
|
|||
jsonld: ['application/ld+json', 'application/json']
|
||||
mapping:
|
||||
paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto']
|
||||
use_symfony_listeners: true
|
||||
defaults:
|
||||
pagination_client_items_per_page: true
|
||||
denormalization_context:
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Dto\Input\UserInput;
|
||||
use App\Entity\User;
|
||||
use App\Handler\ResetPasswordHandler;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
|
||||
#[AsController]
|
||||
class ResetPasswordAction extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResetPasswordHandler $resetPasswordHandler,
|
||||
private readonly UserRepository $userRepository
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function __invoke(string $uuid, UserInput $input): UserInput
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->userRepository->findOneByUuid($uuid);
|
||||
$this->resetPasswordHandler->handle($user, $input->currentPassword, $input->newPassword);
|
||||
|
||||
return new UserInput($user);
|
||||
}
|
||||
}
|
|
@ -13,32 +13,47 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
final class UserInput
|
||||
{
|
||||
#[Assert\NotBlank]
|
||||
#[Groups(['user:write'])]
|
||||
#[Groups('user:write')]
|
||||
public ?string $username = null;
|
||||
|
||||
/**
|
||||
* @var OrganizationalUnit[]
|
||||
*/
|
||||
#[Groups(['user:write'])]
|
||||
#[Groups('user:write')]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
public array $allowedOrganizationalUnits = [];
|
||||
|
||||
#[Assert\NotBlank(groups: ['user:post'])]
|
||||
#[Assert\Length(min: 8, groups: ['user:write', 'user:post'])]
|
||||
#[Groups(['user:write'])]
|
||||
#[Groups('user:write')]
|
||||
public ?string $password = null;
|
||||
|
||||
#[Assert\NotNull]
|
||||
#[Groups(['user:write'])]
|
||||
#[Groups('user:write')]
|
||||
public ?bool $enabled = true;
|
||||
|
||||
/**
|
||||
* @var UserGroup[]
|
||||
*/
|
||||
#[Groups(['user:write'])]
|
||||
#[Groups('user:write')]
|
||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||
public array $userGroups = [];
|
||||
|
||||
#[Assert\NotBlank(groups: ['user:reset-password'])]
|
||||
#[Groups('user:reset-password')]
|
||||
public ?string $currentPassword = null;
|
||||
|
||||
#[Assert\NotBlank(groups: ['user:reset-password'])]
|
||||
#[Assert\Length(min: 8, groups: ['user:reset-password'])]
|
||||
#[Groups('user:reset-password')]
|
||||
public ?string $newPassword = null;
|
||||
|
||||
#[Assert\NotBlank(groups: ['user:reset-password'])]
|
||||
#[Assert\Length(min: 8, groups: ['user:reset-password'])]
|
||||
#[Assert\Expression(expression: 'this.newPassword === this.repeatNewPassword', message: 'This value should be the same as the new password', groups: ['user:reset-password'])]
|
||||
#[Groups('user:reset-password')]
|
||||
public ?string $repeatNewPassword = null;
|
||||
|
||||
public function __construct(?User $user = null)
|
||||
{
|
||||
if (!$user) {
|
||||
|
@ -74,6 +89,18 @@ final class UserInput
|
|||
}
|
||||
$user->setAllowedOrganizationalUnits( $allowedOrganizationalUnitToAdd ?? [] );
|
||||
|
||||
if ($this->currentPassword !== null) {
|
||||
$user->setCurrentPassword($this->currentPassword);
|
||||
}
|
||||
|
||||
if ($this->newPassword !== null) {
|
||||
$user->setNewPassword($this->newPassword);
|
||||
}
|
||||
|
||||
if ($this->repeatNewPassword !== null) {
|
||||
$user->setRepeatNewPassword($this->repeatNewPassword);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
|
@ -47,6 +47,11 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
|
|||
#[ORM\ManyToMany(targetEntity: OrganizationalUnit::class, inversedBy: 'users')]
|
||||
private Collection $allowedOrganizationalUnits;
|
||||
|
||||
private ?string $currentPassword = null;
|
||||
private ?string $newPassword = null;
|
||||
private ?string $repeatNewPassword = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
@ -204,4 +209,40 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCurrentPassword(): ?string
|
||||
{
|
||||
return $this->currentPassword;
|
||||
}
|
||||
|
||||
public function setCurrentPassword(?string $currentPassword): static
|
||||
{
|
||||
$this->currentPassword = $currentPassword;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNewPassword(): ?string
|
||||
{
|
||||
return $this->newPassword;
|
||||
}
|
||||
|
||||
public function setNewPassword(?string $newPassword): static
|
||||
{
|
||||
$this->newPassword = $newPassword;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepeatNewPassword(): ?string
|
||||
{
|
||||
return $this->repeatNewPassword;
|
||||
}
|
||||
|
||||
public function setRepeatNewPassword(?string $repeatNewPassword): static
|
||||
{
|
||||
$this->repeatNewPassword = $repeatNewPassword;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Handler;
|
||||
|
||||
use App\Dto\Input\UserInput;
|
||||
use App\Entity\User;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class ResetPasswordHandler
|
||||
{
|
||||
public function __construct(
|
||||
Security $security,
|
||||
private readonly UserPasswordHasherInterface $userPasswordHasher
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
public function handle(User $user, string $currentPassword, string $newPassword): User
|
||||
{
|
||||
$currentHashedPassword = $this->userPasswordHasher->isPasswordValid($user, $currentPassword);
|
||||
if ($currentHashedPassword === false) {
|
||||
throw new \InvalidArgumentException('The current password is invalid.');
|
||||
}
|
||||
|
||||
$user->setPassword($this->userPasswordHasher->hashPassword($user, $newPassword));
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ use ApiPlatform\Metadata\Get;
|
|||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\State\Pagination\TraversablePaginator;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
|
|
Loading…
Reference in New Issue