login WIP astier

This commit is contained in:
ASTIER Yann 2024-11-28 15:00:06 +01:00
parent adbfe09f01
commit 3ab2ef5a9e
13 changed files with 27 additions and 358 deletions

View File

@ -42,7 +42,6 @@
"symfony/validator": "7.1.*",
"symfony/web-link": "7.1.*",
"symfony/yaml": "7.1.*",
"symfonycasts/verify-email-bundle": "^1.17",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
},

48
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fa6b415ac11ece0fbfc562be9a5d1df3",
"content-hash": "6a957fcd0a6de48ba22ea99ed82f113f",
"packages": [
{
"name": "composer/semver",
@ -7342,52 +7342,6 @@
],
"time": "2024-08-12T09:59:40+00:00"
},
{
"name": "symfonycasts/verify-email-bundle",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/SymfonyCasts/verify-email-bundle.git",
"reference": "f72af149070b39ef82a7095074378d0a98b4d2ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SymfonyCasts/verify-email-bundle/zipball/f72af149070b39ef82a7095074378d0a98b4d2ef",
"reference": "f72af149070b39ef82a7095074378d0a98b4d2ef",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=8.1",
"symfony/config": "^5.4 | ^6.0 | ^7.0",
"symfony/dependency-injection": "^5.4 | ^6.0 | ^7.0",
"symfony/deprecation-contracts": "^2.2 | ^3.0",
"symfony/http-kernel": "^5.4 | ^6.0 | ^7.0",
"symfony/routing": "^5.4 | ^6.0 | ^7.0"
},
"require-dev": {
"doctrine/orm": "^2.7",
"doctrine/persistence": "^2.0",
"symfony/framework-bundle": "^5.4 | ^6.0 | ^7.0",
"symfony/phpunit-bridge": "^5.4 | ^6.0 | ^7.0"
},
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"SymfonyCasts\\Bundle\\VerifyEmail\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Simple, stylish Email Verification for Symfony",
"support": {
"issues": "https://github.com/SymfonyCasts/verify-email-bundle/issues",
"source": "https://github.com/SymfonyCasts/verify-email-bundle/tree/v1.17.0"
},
"time": "2024-03-17T02:29:53+00:00"
},
{
"name": "twig/extra-bundle",
"version": "v3.13.0",

View File

@ -13,5 +13,4 @@ return [
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
];

View File

@ -30,6 +30,11 @@ final class EmployeeController extends AbstractController
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$employee->setPassword(
password_hash($form->get('plainPassword')->getData(), PASSWORD_BCRYPT)
);
$entityManager->persist($employee);
$entityManager->flush();

View File

@ -1,86 +0,0 @@
<?php
namespace App\Controller;
use App\Entity\Employee;
use App\Form\RegistrationFormType;
use App\Security\EmailVerifier;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
class RegistrationController extends AbstractController
{
public function __construct(private EmailVerifier $emailVerifier)
{
}
#[Route('/register', name: 'app_register')]
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, Security $security, EntityManagerInterface $entityManager): Response
{
$employee = new Employee();
$form = $this->createForm(RegistrationFormType::class, $employee);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$employee->setPassword(
password_hash($form->get('plainPassword')->getData(), PASSWORD_BCRYPT)
);
$entityManager->persist($employee);
$entityManager->flush();
// generate a signed url and email it to the employee
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $employee,
(new TemplatedEmail())
->from(new Address('no-reply@HegreLand.com', 'Hegre Land'))
->to((string) $employee->getEmail())
->subject('Please Confirm your Email')
->htmlTemplate('registration/confirmation_email.html.twig')
);
// do anything else you need here, like send an email
return $security->login($employee, LoginFormAuthenticator::class, 'main');
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form,
]);
}
#[Route('/verify/email', name: 'app_verify_email')]
public function verifyUserEmail(Request $request, TranslatorInterface $translator): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
// validate email confirmation link, sets User::isVerified=true and persists
try {
/** @var Employee $employee */
$employee = $this->getUser();
$this->emailVerifier->handleEmailConfirmation($request, $employee);
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle'));
return $this->redirectToRoute('DashboardController');
}
// @TODO Change the redirect on success and handle or remove the flash message in your templates
$this->addFlash('success', 'Your email address has been verified.');
return $this->redirectToRoute('app_register');
}
}

View File

@ -7,9 +7,10 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
#[Route(path: '/login', name: 'security')]
class SecurityController extends AbstractController
{
#[Route(path: '/login', name: 'app_login')]
#[Route(path: '', name: '_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// if ($this->getUser()) {

View File

@ -10,6 +10,8 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class EmployeeType extends AbstractType
{
@ -19,7 +21,6 @@ class EmployeeType extends AbstractType
->add('email', EmailType::class, ['label' => 'Email Address'])
->add('firstName', TextType::class, ['label' => 'First Name'])
->add('lastName', TextType::class, ['label' => 'Last Name'])
->add('password', PasswordType::class, ['label' => 'Password'])
->add('roles', ChoiceType::class, [
'label' => 'Roles (comma-separated)',
'required' => false,
@ -30,7 +31,23 @@ class EmployeeType extends AbstractType
'multiple' => true, // Allow multiple selections
'expanded' => true, // Render as checkboxes
])
;
->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
]);
}
public function configureOptions(OptionsResolver $resolver): void

View File

@ -1,64 +0,0 @@
<?php
namespace App\Form;
use App\Entity\Employee;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, ['label' => 'Email Address'])
->add('firstName', TextType::class, ['label' => 'First Name'])
->add('lastName', TextType::class, ['label' => 'Last Name'])
->add('roles', ChoiceType::class, [
'label' => 'Roles (comma-separated)',
'required' => false,
'choices' => [
'User' => 'ROLE_USER',
'Admin' => 'ROLE_ADMIN',
],
'multiple' => true, // Allow multiple selections
'expanded' => true, // Render as checkboxes
])
->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
->add('save', SubmitType::class, ['label' => 'Add Employee'])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Employee::class,
]);
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
/**
* @extends ServiceEntityRepository<User>
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
}
$user->setPassword($newHashedPassword);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
// /**
// * @return User[] Returns an array of User objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?User
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -1,52 +0,0 @@
<?php
namespace App\Security;
use App\Entity\Employee;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
class EmailVerifier
{
public function __construct(
private VerifyEmailHelperInterface $verifyEmailHelper,
private MailerInterface $mailer,
private EntityManagerInterface $entityManager
) {
}
public function sendEmailConfirmation(string $verifyEmailRouteName, Employee $employee, TemplatedEmail $email): void
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
$verifyEmailRouteName,
(string) $employee->getId(),
(string) $employee->getEmail()
);
$context = $email->getContext();
$context['signedUrl'] = $signatureComponents->getSignedUrl();
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
$email->context($context);
}
/**
* @throws VerifyEmailExceptionInterface
*/
public function handleEmailConfirmation(Request $request, Employee $employee): void
{
$this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $employee->getId(), (string) $employee->getEmail());
$employee->setVerified(true);
$this->entityManager->persist($employee);
$this->entityManager->flush();
}
}

View File

@ -1,11 +0,0 @@
<h1>Hi! Please confirm your email!</h1>
<p>
Please confirm your email address by clicking the following link: <br><br>
<a href="{{ signedUrl|raw }}">Confirm my Email</a>.
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
</p>
<p>
Cheers!
</p>

View File

@ -1,32 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}HegreLand{% endblock %}
{% block body %}
{% for flash_error in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<h1>M'inscrire</h1>
{{ form_errors(registrationForm) }}
{{ form_start(registrationForm) }}
<div>
{{ form_row(registrationForm.email) }}
{{ form_row(registrationForm.firstName) }}
{{ form_row(registrationForm.lastName) }}
{{ form_row(registrationForm.roles) }}
</div>
<div>
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
</div>
<button type="submit" class="btn">M'inscrire</button>
<p>Déjà inscrit(e) ?<a href="{{ path('app_login') }}"> Me connecter</a> </p>
{{ form_end(registrationForm) }}
{% endblock %}

View File

@ -21,6 +21,5 @@
Se connecter
</button>
<p><a href="#"> Mot de passe oublié</a> </p>
<p>Pas encore inscrit(e) ?<a href="{{ path('app_register') }}"> M'inscrire</a> </p>
</form>
{% endblock %}