Merge pull request 'merge_ResetPassword_to_Security' (#1) from merge_ResetPassword_to_Security into master
Reviewed-on: tracer/24unix.net.web#1
This commit is contained in:
commit
6e9a07b62c
|
@ -24,11 +24,8 @@ security:
|
|||
provider: app_user_provider
|
||||
custom_authenticator: App\Security\LoginFormAuthenticator
|
||||
|
||||
##form_login:
|
||||
# login_path: app_login
|
||||
# check_path: app_login
|
||||
logout:
|
||||
path: app_logout
|
||||
path: security_logout
|
||||
switch_user: true
|
||||
|
||||
remember_me:
|
||||
|
|
|
@ -6,7 +6,6 @@ declare(strict_types=1);
|
|||
namespace App\Controller;
|
||||
|
||||
use App\Repository\QuotesRepository;
|
||||
use Exception;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
@ -27,13 +26,4 @@ class FrontendController extends AbstractController
|
|||
'quote' => json_encode(value: $quote->getQuote())
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Route(path: '/logout', name: 'app_logout')]
|
||||
public function logout(): never
|
||||
{
|
||||
throw new Exception(message: 'Logout should never be reached.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\ChangePasswordFormType;
|
||||
use App\Form\ResetPasswordRequestFormType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
|
||||
|
||||
class ResetPasswordController extends AbstractController
|
||||
{
|
||||
use ResetPasswordControllerTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly ResetPasswordHelperInterface $resetPasswordHelper,
|
||||
private readonly EntityManagerInterface $entityManager
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
/**
|
||||
* Display & process form to request a password reset.
|
||||
*/
|
||||
#[Route(path: '/security/forgot/password', name: 'security_forgot_password')]
|
||||
public function request(Request $request, MailerInterface $mailer): Response
|
||||
{
|
||||
$form = $this->createForm(type: ResetPasswordRequestFormType::class);
|
||||
$form->handleRequest(request: $request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
return $this->processSendingPasswordResetEmail(
|
||||
formData: $form->get(name: 'account')->getData(),
|
||||
mailer: $mailer
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render(view: '@default/security/forgot_password.html.twig', parameters: [
|
||||
'requestForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation page after a user has requested a password reset.
|
||||
*/
|
||||
#[Route(path: '/security/recovery/mail/sent', name: 'security_recovery_mail_sent')]
|
||||
public function checkEmail(): Response
|
||||
{
|
||||
// Generate a fake token if the user does not exist or someone hit this page directly.
|
||||
// This prevents exposing whether a user was found with the given email address or username or not
|
||||
if (null === ($resetToken = $this->getTokenObjectFromSession())) {
|
||||
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
|
||||
}
|
||||
|
||||
return $this->render(view: '@default/security/recovery_mail_sent.html.twig', parameters: [
|
||||
'resetToken' => $resetToken,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and process the reset URL that the user clicked in their email.
|
||||
*/
|
||||
#[Route(path: '/security/recovery/reset/{token}', name: 'security_recovery_reset')]
|
||||
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response
|
||||
{
|
||||
if ($token) {
|
||||
// We store the token in session and remove it from the URL, to avoid the URL being
|
||||
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
|
||||
$this->storeTokenInSession(token: $token);
|
||||
|
||||
return $this->redirectToRoute(route: 'security_recovery_reset');
|
||||
}
|
||||
|
||||
$token = $this->getTokenFromSession();
|
||||
if (null === $token) {
|
||||
throw $this->createNotFoundException(message: 'No reset password token found in the URL or in the session.');
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->resetPasswordHelper->validateTokenAndFetchUser(fullToken: $token);
|
||||
} catch (ResetPasswordExceptionInterface $e) {
|
||||
$this->addFlash(type: 'reset_password_error', message: sprintf(
|
||||
'%s - %s',
|
||||
$translator->trans(id: ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, parameters: [], domain: 'ResetPasswordBundle'),
|
||||
$translator->trans(id: $e->getReason(), parameters: [], domain: 'ResetPasswordBundle')
|
||||
));
|
||||
|
||||
return $this->redirectToRoute(route: 'app_forgot_password_request');
|
||||
}
|
||||
|
||||
// The token is valid; allow the user to change their password.
|
||||
$form = $this->createForm(type: ChangePasswordFormType::class);
|
||||
$form->handleRequest(request: $request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
// A password reset token should be used only once, remove it.
|
||||
$this->resetPasswordHelper->removeResetRequest(fullToken: $token);
|
||||
|
||||
// Encode(hash) the plain password, and set it.
|
||||
$encodedPassword = $passwordHasher->hashPassword(
|
||||
user: $user,
|
||||
plainPassword: $form->get(name: 'plainPassword')->getData()
|
||||
);
|
||||
|
||||
$user->setPassword($encodedPassword);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// The session is cleaned up after the password has been changed.
|
||||
$this->cleanSessionAfterReset();
|
||||
|
||||
$this->addFlash(type: 'success', message: 'Your password has been changed.');
|
||||
|
||||
return $this->redirectToRoute(route: 'app_main');
|
||||
}
|
||||
|
||||
return $this->render(view: '@default/security/reset_password.html.twig', parameters: [
|
||||
'resetForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function processSendingPasswordResetEmail(string $formData, MailerInterface $mailer): RedirectResponse
|
||||
{
|
||||
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
|
||||
'email' => $formData,
|
||||
]);
|
||||
|
||||
if (!$user) {
|
||||
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
|
||||
'username' => $formData,
|
||||
]);
|
||||
}
|
||||
|
||||
// Do not reveal whether a user account was found or not.
|
||||
// if (!$user) {
|
||||
// return $this->redirectToRoute(route: 'app_check_email');
|
||||
// }
|
||||
|
||||
try {
|
||||
$resetToken = $this->resetPasswordHelper->generateResetToken(user: $user);
|
||||
} catch (ResetPasswordExceptionInterface $e) {
|
||||
$this->addFlash(type: 'reset_password_error', message: sprintf(
|
||||
'%s - %s',
|
||||
ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE,
|
||||
$e->getReason()
|
||||
));
|
||||
|
||||
return $this->redirectToRoute(route: 'security_forgot_password');
|
||||
}
|
||||
|
||||
$email = (new TemplatedEmail())
|
||||
->from(new Address(address: 'tracer@24unix.net', name: '24unix.net'))
|
||||
->to($user->getEmail())
|
||||
->subject(subject: 'Your password reset request')
|
||||
->htmlTemplate(template: '@default/security/mail/recovery.html.twig')
|
||||
->context(context: [
|
||||
'resetToken' => $resetToken,
|
||||
]);
|
||||
|
||||
$mailer->send(message: $email);
|
||||
|
||||
// Store the token object in session for retrieval in check-email route.
|
||||
$this->setTokenObjectInSession(token: $resetToken);
|
||||
|
||||
return $this->redirectToRoute(route: 'security_recovery_mail_sent');
|
||||
}
|
||||
}
|
|
@ -2,31 +2,44 @@
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Form\LoginFormType;
|
||||
use App\Entity\User;
|
||||
use App\Form\ChangePasswordFormType;
|
||||
use App\Form\RegistrationFormType;
|
||||
use App\Form\ResetPasswordRequestFormType;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Security\EmailVerifier;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
|
||||
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
use ResetPasswordControllerTrait;
|
||||
|
||||
public function __construct(private readonly EmailVerifier $emailVerifier)
|
||||
public function __construct(private readonly EmailVerifier $emailVerifier,
|
||||
private readonly ResetPasswordHelperInterface $resetPasswordHelper,
|
||||
private readonly EntityManagerInterface $entityManager
|
||||
)
|
||||
{
|
||||
// empty body
|
||||
}
|
||||
|
||||
#[Route(path: '/login', name: 'app_login')]
|
||||
#[Route(path: '/security/login', name: 'security_login')]
|
||||
public function index(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
// get the login error if there is one
|
||||
|
@ -43,8 +56,16 @@ class SecurityController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Route(path: '/security/logout', name: 'security_logout')]
|
||||
public function logout(): never
|
||||
{
|
||||
throw new Exception(message: 'Logout should never be reached.');
|
||||
}
|
||||
|
||||
#[Route(path: '/register', name: 'app_register')]
|
||||
#[Route(path: '/security/register', name: 'security_register')]
|
||||
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$form = $this->createForm(type: RegistrationFormType::class);
|
||||
|
@ -67,7 +88,7 @@ class SecurityController extends AbstractController
|
|||
$entityManager->flush();
|
||||
|
||||
// generate a signed url and email it to the user
|
||||
$this->emailVerifier->sendEmailConfirmation(verifyEmailRouteName: 'app_verify_email', user: $user,
|
||||
$this->emailVerifier->sendEmailConfirmation(verifyEmailRouteName: 'security_verify_email', user: $user,
|
||||
email: (new TemplatedEmail())
|
||||
->from(new Address(address: 'info@24unix.net', name: '24unix.net'))
|
||||
->to($user->getEmail())
|
||||
|
@ -86,7 +107,7 @@ class SecurityController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/verify/email', name: 'app_verify_email')]
|
||||
#[Route(path: '/security/verify/email', name: 'security_verify_email')]
|
||||
public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
|
||||
{
|
||||
$id = $request->get(key: 'id');
|
||||
|
@ -115,4 +136,152 @@ class SecurityController extends AbstractController
|
|||
return $this->redirectToRoute(route: 'app_main');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display & process form to request a password reset.
|
||||
*/
|
||||
#[Route(path: '/security/forgot/password', name: 'security_forgot_password')]
|
||||
public function request(Request $request, MailerInterface $mailer): Response
|
||||
{
|
||||
$form = $this->createForm(type: ResetPasswordRequestFormType::class);
|
||||
$form->handleRequest(request: $request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
return $this->processSendingPasswordResetEmail(
|
||||
formData: $form->get(name: 'account')->getData(),
|
||||
mailer: $mailer
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render(view: '@default/security/forgot_password.html.twig', parameters: [
|
||||
'requestForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation page after a user has requested a password reset.
|
||||
*/
|
||||
#[Route(path: '/security/recovery/mail/sent', name: 'security_recovery_mail_sent')]
|
||||
public function checkEmail(): Response
|
||||
{
|
||||
// Generate a fake token if the user does not exist or someone hit this page directly.
|
||||
// This prevents exposing whether a user was found with the given email address or username or not
|
||||
if (null === ($resetToken = $this->getTokenObjectFromSession())) {
|
||||
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
|
||||
}
|
||||
|
||||
return $this->render(view: '@default/security/recovery_mail_sent.html.twig', parameters: [
|
||||
'resetToken' => $resetToken,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and process the reset URL that the user clicked in their email.
|
||||
*/
|
||||
#[Route(path: '/security/recovery/reset/{token}', name: 'security_recovery_reset')]
|
||||
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response
|
||||
{
|
||||
if ($token) {
|
||||
// We store the token in session and remove it from the URL, to avoid the URL being
|
||||
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
|
||||
$this->storeTokenInSession(token: $token);
|
||||
|
||||
return $this->redirectToRoute(route: 'security_recovery_reset');
|
||||
}
|
||||
|
||||
$token = $this->getTokenFromSession();
|
||||
if (null === $token) {
|
||||
throw $this->createNotFoundException(message: 'No reset password token found in the URL or in the session.');
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->resetPasswordHelper->validateTokenAndFetchUser(fullToken: $token);
|
||||
} catch (ResetPasswordExceptionInterface $e) {
|
||||
$this->addFlash(type: 'reset_password_error', message: sprintf(
|
||||
'%s - %s',
|
||||
$translator->trans(id: ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, parameters: [], domain: 'ResetPasswordBundle'),
|
||||
$translator->trans(id: $e->getReason(), parameters: [], domain: 'ResetPasswordBundle')
|
||||
));
|
||||
|
||||
return $this->redirectToRoute(route: 'app_forgot_password_request');
|
||||
}
|
||||
|
||||
// The token is valid; allow the user to change their password.
|
||||
$form = $this->createForm(type: ChangePasswordFormType::class);
|
||||
$form->handleRequest(request: $request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
// A password reset token should be used only once, remove it.
|
||||
$this->resetPasswordHelper->removeResetRequest(fullToken: $token);
|
||||
|
||||
// Encode(hash) the plain password, and set it.
|
||||
$encodedPassword = $passwordHasher->hashPassword(
|
||||
user: $user,
|
||||
plainPassword: $form->get(name: 'plainPassword')->getData()
|
||||
);
|
||||
|
||||
$user->setPassword($encodedPassword);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// The session is cleaned up after the password has been changed.
|
||||
$this->cleanSessionAfterReset();
|
||||
|
||||
$this->addFlash(type: 'success', message: 'Your password has been changed.');
|
||||
|
||||
return $this->redirectToRoute(route: 'app_main');
|
||||
}
|
||||
|
||||
return $this->render(view: '@default/security/reset_password.html.twig', parameters: [
|
||||
'resetForm' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function processSendingPasswordResetEmail(string $formData, MailerInterface $mailer): RedirectResponse
|
||||
{
|
||||
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
|
||||
'email' => $formData,
|
||||
]);
|
||||
|
||||
if (!$user) {
|
||||
$user = $this->entityManager->getRepository(entityName: User::class)->findOneBy(criteria: [
|
||||
'username' => $formData,
|
||||
]);
|
||||
}
|
||||
|
||||
// Do not reveal whether a user account was found or not.
|
||||
// if (!$user) {
|
||||
// return $this->redirectToRoute(route: 'app_check_email');
|
||||
// }
|
||||
|
||||
try {
|
||||
$resetToken = $this->resetPasswordHelper->generateResetToken(user: $user);
|
||||
} catch (ResetPasswordExceptionInterface $e) {
|
||||
$this->addFlash(type: 'reset_password_error', message: sprintf(
|
||||
'%s - %s',
|
||||
ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE,
|
||||
$e->getReason()
|
||||
));
|
||||
|
||||
return $this->redirectToRoute(route: 'security_forgot_password');
|
||||
}
|
||||
|
||||
$email = (new TemplatedEmail())
|
||||
->from(new Address(address: 'tracer@24unix.net', name: '24unix.net'))
|
||||
->to($user->getEmail())
|
||||
->subject(subject: 'Your password reset request')
|
||||
->htmlTemplate(template: '@default/security/mail/recovery.html.twig')
|
||||
->context(context: [
|
||||
'resetToken' => $resetToken,
|
||||
]);
|
||||
|
||||
try {
|
||||
$mailer->send(message: $email);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->addFlash(type: 'error', message: $e->getMessage());
|
||||
}
|
||||
|
||||
// Store the token object in session for retrieval in check-email route.
|
||||
$this->setTokenObjectInSession(token: $resetToken);
|
||||
|
||||
return $this->redirectToRoute(route: 'security_recovery_mail_sent');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use JetBrains\PhpStorm\ArrayShape;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use function is_array;
|
||||
|
||||
class UserNotVerifiedException extends AuthenticationException
|
||||
{
|
||||
private ?string $identifier = null;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessageKey(): string
|
||||
{
|
||||
return 'User is not verified.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user identifier (e.g. username or email address).
|
||||
*/
|
||||
public function getUserIdentifier(): ?string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user identifier (e.g. username or email address).
|
||||
*/
|
||||
public function setUserIdentifier(string $identifier): void
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[ArrayShape(shape: ['{{ username }}' => "null|string", '{{ user_identifier }}' => "null|string"])]
|
||||
public function getMessageData(): array
|
||||
{
|
||||
return ['{{ username }}' => $this->identifier, '{{ user_identifier }}' => $this->identifier];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->identifier, parent::__serialize()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->identifier, $parentData] = $data;
|
||||
$parentData = is_array(value: $parentData) ? $parentData : unserialize(data: $parentData);
|
||||
parent::__unserialize(data: $parentData);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,10 @@ namespace App\Repository;
|
|||
|
||||
use App\Entity\Quotes;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @method Quotes|null find($id, $lockMode = null, $lockVersion = null)
|
||||
|
@ -21,7 +24,7 @@ class QuotesRepository extends ServiceEntityRepository
|
|||
|
||||
public function add(Quotes $entity, bool $flush = true): void
|
||||
{
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->persist(entity: $entity);
|
||||
if ($flush) {
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
@ -29,31 +32,40 @@ class QuotesRepository extends ServiceEntityRepository
|
|||
|
||||
public function remove(Quotes $entity, bool $flush = true): void
|
||||
{
|
||||
$this->_em->remove($entity);
|
||||
$this->_em->remove(entity: $entity);
|
||||
if ($flush) {
|
||||
$this->_em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int|mixed|string|null
|
||||
*
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function findOneRandom(): mixed
|
||||
public function findOneRandom(): ?QueryBuilder
|
||||
{
|
||||
try {
|
||||
$idLimits = $this->createQueryBuilder(alias: 'q')
|
||||
->select('MIN(q.id)', 'MAX(q.id)')
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
$randomPossibleId = random_int(min: $idLimits[1], max: $idLimits[2]);
|
||||
} catch (NonUniqueResultException) {
|
||||
$idLimits = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
$randomPossibleId = random_int(min: $idLimits[1], max: $idLimits[2]);
|
||||
} catch(Exception) {
|
||||
$randomPossibleId = 0; // return first, if any
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->createQueryBuilder(alias: 'q')
|
||||
->where(predicates: 'q.id >= :random_id')
|
||||
->setParameter(key: 'random_id', value: $randomPossibleId)
|
||||
->setMaxResults(maxResults: 1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
} catch (NonUniqueResultException) {
|
||||
// max results is 1
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
|
|
|
@ -2,28 +2,21 @@
|
|||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\LoginFormType;
|
||||
use App\Exception\UserNotVerifiedException;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||
|
||||
/**
|
||||
|
@ -58,6 +51,10 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
|
|||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
if (!$user->isVerified()) {
|
||||
throw new UserNotVerifiedException();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}),
|
||||
|
||||
|
@ -82,7 +79,7 @@ class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
|
|||
|
||||
protected function getLoginUrl(Request $request): string
|
||||
{
|
||||
return $this->router->generate(name: 'app_login');
|
||||
return $this->router->generate(name: 'security_login');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<div class="dropdown-divider"></div>
|
||||
{% endif %}
|
||||
|
||||
<a class="dropdown-item" href="{{ path('app_logout') }}">
|
||||
<a class="dropdown-item" href="{{ path('security_logout') }}">
|
||||
<span class="fa fa-lg fa-fw fa-sign-out" aria-hidden="true"></span>
|
||||
Logout
|
||||
</a>
|
||||
|
@ -50,7 +50,7 @@
|
|||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-primary button-login" href="{{ path('app_login') }}" role="button"
|
||||
<a class="btn btn-primary button-login" href="{{ path('security_login') }}" role="button"
|
||||
id="buttonLogin">
|
||||
<span class="fa fa-sign-in fa-lg fa-fw" aria-hidden="true"></span>Log In
|
||||
</a>
|
||||
|
|
|
@ -27,14 +27,18 @@
|
|||
<label for="inputPassword">Password</label>
|
||||
<input type="password" name="password" id="inputPassword" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<div class="form-check mx-3">
|
||||
<label>
|
||||
<input type="checkbox" name="_remember_me" class="form-check-input">Remember me
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary float-end" type="submit">
|
||||
<div>
|
||||
<a href="{{ path('security_forgot_password') }}">Forgot password?</a>
|
||||
<button class="btn btn-primary float-end" type="submit">
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
<a href="{{ path('security_register') }}">Need an account? Register now.</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
|
||||
{{ form_start(resetForm) }}
|
||||
{{ form_row(resetForm.plainPassword) }}
|
||||
<button class="btn btn-primary">Reset password</button>
|
||||
<button class="btn btn-primary">Update password</button>
|
||||
{{ form_end(resetForm) }}
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue