. */ declare(strict_types=1); namespace App\Services\UserSystem; use App\Entity\UserSystem\User; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Address; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\PasswordHasherInterface; use Symfony\Contracts\Translation\TranslatorInterface; class PasswordResetManager { protected PasswordHasherInterface $passwordEncoder; public function __construct(protected MailerInterface $mailer, protected EntityManagerInterface $em, protected TranslatorInterface $translator, protected UserPasswordHasherInterface $userPasswordEncoder, PasswordHasherFactoryInterface $encoderFactory) { $this->passwordEncoder = $encoderFactory->getPasswordHasher(User::class); } public function request(string $name_or_email): void { $repo = $this->em->getRepository(User::class); //Try to find a user by the given string $user = $repo->findByEmailOrName($name_or_email); //Do nothing if no user was found if (!$user instanceof User) { return; } $unencrypted_token = md5(random_bytes(32)); $user->setPwResetToken($this->passwordEncoder->hash($unencrypted_token)); //Determine the expiration datetime of $expiration_date = new DateTime(); $expiration_date->add(date_interval_create_from_date_string('1 day')); $user->setPwResetExpires($expiration_date); if (!empty($user->getEmail())) { $address = new Address($user->getEmail(), $user->getFullName()); $mail = new TemplatedEmail(); $mail->to($address); $mail->subject($this->translator->trans('pw_reset.email.subject')); $mail->htmlTemplate('mail/pw_reset.html.twig'); $mail->context([ 'expiration_date' => $expiration_date, 'token' => $unencrypted_token, 'user' => $user, ]); //Send email $this->mailer->send($mail); } //Save changes to DB $this->em->flush(); } /** * Sets the new password of the user with the given name, if the token is valid. * * @param string $username The name of the user, which password should be reset * @param string $token The token that should be used to reset the password * @param string $new_password The new password that should be applied to user * * @return bool Returns true, if the new password was applied. False, if either the username is unknown or the * token is invalid or expired. */ public function setNewPassword(string $username, string $token, string $new_password): bool { //Try to find the user $repo = $this->em->getRepository(User::class); /** @var User|null $user */ $user = $repo->findOneBy(['name' => $username]); //If no user matching the name, show an error message if (!$user instanceof User) { return false; } //Check if token is expired yet if ($user->getPwResetExpires() < new DateTime()) { return false; } //Check if token is valid if (!$this->passwordEncoder->verify($user->getPwResetToken(), $token)) { return false; } //When everything was valid, apply the new password $user->setPassword($this->userPasswordEncoder->hashPassword($user, $new_password)); //Remove token $user->setPwResetToken(null); $user->setPwResetExpires(new DateTime()); //Save to DB $this->em->flush(); return true; } }