Last active
December 30, 2022 06:06
-
-
Save denistouch/88a338bac344a6d100c5acd7dc140cec to your computer and use it in GitHub Desktop.
Восстановление пароля через почту
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Subscriber; | |
use App\Event\PasswordRestoreCreateEvent; | |
use App\Service\MailSendService; | |
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
class PasswordChangeSubscriber implements EventSubscriberInterface | |
{ | |
public function __construct( | |
private MailSendService $sendService, | |
private string $passwordUrlTemplate, | |
private string $codePasswordExpiredTtl | |
) { | |
} | |
public static function getSubscribedEvents() | |
{ | |
return [ | |
PasswordRestoreCreateEvent::NAME => 'onRestorePassword' | |
]; | |
} | |
public function onRestorePassword(PasswordRestoreCreateEvent $event) | |
{ | |
$updatedPassword = $event->getUpdatedPassword(); | |
$emailTo = $updatedPassword->getUser()->getEmail(); | |
$link = $this->createRestoreLink($updatedPassword->getCode()); | |
$expiredAt = $this->codePasswordExpiredTtl; | |
$parameters = [ | |
'updatedPassword' => $updatedPassword, | |
'confirm_link' => $link, | |
'expiration_time' => $expiredAt, | |
]; | |
$this->sendService->sendMail( | |
$emailTo, | |
'password/restore.html.twig', | |
$parameters | |
); | |
} | |
private function createRestoreLink(string $code): string | |
{ | |
return str_replace('%CODE%', $code, $this->passwordUrlTemplate); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Event; | |
use App\Entity\UpdatedPassword; | |
use Symfony\Contracts\EventDispatcher\Event; | |
class PasswordRestoreCreateEvent extends Event | |
{ | |
public const NAME = 'password.update.request.create'; | |
public function __construct(private UpdatedPassword $updatedPassword) | |
{ | |
} | |
public function getUpdatedPassword(): UpdatedPassword | |
{ | |
return $this->updatedPassword; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Service; | |
use App\Entity\PasswordRestore; | |
use App\Entity\User; | |
use App\Event\PasswordRestoreCreateEvent; | |
use App\Exception\UserNotFoundException; | |
use App\Model\CodePasswordConfirmRequest; | |
use App\Model\PasswordRestoreRequest; | |
use App\Repository\PasswordRestoreRepository; | |
use App\Util\Value; | |
use DateTime; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | |
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | |
use Symfony\Component\Translation\TranslatableMessage; | |
class PasswordRestoreService | |
{ | |
public function __construct | |
( | |
private readonly CodeService $codeService, | |
private readonly UserService $userService, | |
private readonly PasswordRestoreRepository $passwordRepository, | |
private readonly EventDispatcherInterface $dispatcher, | |
private readonly string $codePasswordExpiredTtl | |
) { | |
} | |
public function createPasswordRestoreRequest(PasswordRestoreRequest $request): void | |
{ | |
$user = $this->userService->getUserByEmail($request->getEmail()); | |
if (!$user) { | |
throw new UserNotFoundException($request->getEmail()); | |
} | |
$passwordRestore = $this->createPasswordRestore($user); | |
$event = new PasswordRestoreCreateEvent($passwordRestore); | |
$this->dispatcher->dispatch($event, PasswordRestoreCreateEvent::NAME); | |
} | |
public function confirmCode(CodePasswordConfirmRequest $request): void | |
{ | |
$passwordRestore = $this->passwordRepository->findOneBy(['code' => $request->getCode()]); | |
if (!$passwordRestore) { | |
throw new NotFoundHttpException(new TranslatableMessage('code.found.error')); | |
} | |
$expiredDate = Value::getDateTimeSubSeconds(new DateTime(), $this->codePasswordExpiredTtl); | |
if ($passwordRestore->getCreatedAt() < $expiredDate) { | |
throw new AccessDeniedException(new TranslatableMessage('code.expired')); | |
} | |
$user = $passwordRestore->getUser(); | |
$this->userService->passwordRestore($user, $request->getPassword()); | |
$this->passwordRepository->remove($passwordRestore); | |
} | |
private function createPasswordRestore(User $user): PasswordRestore | |
{ | |
$passwordRestore = new PasswordRestore(); | |
$passwordRestore->setUser($user); | |
$passwordRestore->setCode($this->codeService->generate()); | |
$this->passwordRepository->add($passwordRestore); | |
return $passwordRestore; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Service; | |
use App\Entity\User; | |
use App\Model\PasswordChangeRequest; | |
use App\Model\UserListItem; | |
use App\Model\UserListResponse; | |
use App\Repository\UserRepository; | |
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; | |
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | |
use Symfony\Component\Translation\TranslatableMessage; | |
class UserService | |
{ | |
public function __construct( | |
private readonly UserRepository $userRepository, | |
private readonly UserPasswordHasherInterface $hasher | |
) { | |
} | |
public function getUsers(): UserListResponse | |
{ | |
$items = collect($this->userRepository->findAll())->map( | |
fn(User $user) => new UserListItem($user->getId(), $user->getEmail(), $user->getCode()) | |
)->all(); | |
return new UserListResponse($items); | |
} | |
public function updateEmail(User $user, $newEmail): void | |
{ | |
$this->userRepository->updateEmail($user, $newEmail); | |
$this->addToUserRoleUSER($user); | |
} | |
public function passwordRestore(User $user, string $password): void | |
{ | |
$hashedPassword = $this->hasher->hashPassword($user, $password); | |
$this->userRepository->upgradePassword($user, $hashedPassword); | |
$this->addToUserRoleUSER($user); | |
} | |
public function create(User $user): void | |
{ | |
$user->setRoles([User::ROLE_GUEST]); | |
$this->userRepository->add($user); | |
} | |
public function existsByEmail(string $email): bool | |
{ | |
return $this->userRepository->existsByEmail($email); | |
} | |
public function getUserByEmail(string $email): ?User | |
{ | |
return $this->userRepository->loadUserByIdentifier($email); | |
} | |
public function isUserHasOnlyRole(User $user, string $role): bool | |
{ | |
return (in_array($role, $user->getRoles()) && count($user->getRoles()) === 1); | |
} | |
public function changePassword(PasswordChangeRequest $request, User $user): void | |
{ | |
$old = $request->getOldPassword(); | |
if (!$this->hasher->isPasswordValid($user, $old)) { | |
throw new AccessDeniedException(new TranslatableMessage('password.correct.error')); | |
} | |
$newHashedPassword = $this->hasher->hashPassword($user, $request->getNewPassword()); | |
$this->userRepository->upgradePassword($user, $newHashedPassword); | |
} | |
private function addToUserRoleUSER(User $user): void | |
{ | |
$roles = $user->getRoles(); | |
if (!in_array(User::ROLE_USER, $roles)) { | |
$roles[] = User::ROLE_USER; | |
$this->userRepository->updateRoles($user, $roles); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment