diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6b3167f..7c33053 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,21 +1,47 @@ security: # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: - Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'plaintext' # à mettre en auto pour sécuriser les mdp # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: # used to reload user from session & other features (e.g. switch_user) - app_user_provider: + app_intern_provider: entity: - class: App\Entity\User + class: App\Entity\Intern property: nickname + + app_employee_provider: + entity: + class: App\Entity\Employee + property: nickname + # used to reload user from session & other features (e.g. switch_user) + # used to reload user from session & other features (e.g. switch_user) firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false - main: + intern: lazy: true - provider: app_user_provider + provider: app_intern_provider + form_login: + login_path: app_login + check_path: app_login + enable_csrf: true + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route + employee: + lazy: true + provider: app_employee_provider + form_login: + login_path: app_login + check_path: app_login + enable_csrf: true + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/migrations/Version20241121153037.php b/migrations/Version20241121153037.php new file mode 100644 index 0000000..9a792b8 --- /dev/null +++ b/migrations/Version20241121153037.php @@ -0,0 +1,31 @@ +addSql('CREATE SCHEMA public'); + } +} diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 0000000..50e84c2 --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,75 @@ +createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var string $plainPassword */ + $plainPassword = $form->get('plainPassword')->getData(); + + $user->setRoles(['ROLE_INTERN']); + // encode the plain password + $user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword)); + + $entityManager->persist($user); + $entityManager->flush(); + + // do anything else you need here, like send an email + + return $security->login($user, 'form_login', 'intern'); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form, + ]); + } + + #[Route('/employee', name: '_employee')] + public function registerEmployee(Request $request, UserPasswordHasherInterface $userPasswordHasher, Security $security, EntityManagerInterface $entityManager): Response + { + $user = new Employee(); + $form = $this->createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var string $plainPassword */ + $plainPassword = $form->get('plainPassword')->getData(); + + $user->setRoles(['ROLE_EMPLOYEE']); + // encode the plain password + $user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword)); + + $entityManager->persist($user); + $entityManager->flush(); + + // do anything else you need here, like send an email + + return $security->login($user, 'form_login', 'employee'); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form, + ]); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..76bf5c4 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,32 @@ +getLastAuthenticationError(); + + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route(path: '/logout', name: 'app_logout')] + public function logout(): void + { + throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); + } +} diff --git a/src/Entity/Announcement.php b/src/Entity/Announcement.php index 6360da7..ed4d371 100644 --- a/src/Entity/Announcement.php +++ b/src/Entity/Announcement.php @@ -96,7 +96,7 @@ class Announcement return $this->status; } - public function setStatus(?Status $status): static + public function setStatus(?string $status): static { $this->status = $status; diff --git a/src/Entity/Employee.php b/src/Entity/Employee.php index 393a529..42f71b3 100644 --- a/src/Entity/Employee.php +++ b/src/Entity/Employee.php @@ -9,7 +9,7 @@ use Doctrine\ORM\Mapping as ORM; class Employee extends UserApp { #[ORM\ManyToOne(inversedBy: 'employees')] - #[ORM\JoinColumn(nullable: false)] + #[ORM\JoinColumn(nullable: true)] private ?Company $company = null; public function getCompany(): ?Company diff --git a/src/Entity/Intern.php b/src/Entity/Intern.php index 2ca1f8e..daaf428 100644 --- a/src/Entity/Intern.php +++ b/src/Entity/Intern.php @@ -12,10 +12,10 @@ use Doctrine\ORM\Mapping as ORM; class Intern extends UserApp { - #[ORM\Column(type: Types::TEXT)] + #[ORM\Column(type: Types::TEXT,nullable: true)] private ?string $coverLetter = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255,nullable: true)] private ?string $resume = null; /** diff --git a/src/Entity/UserApp.php b/src/Entity/UserApp.php index 5c68f4f..7dabaa2 100644 --- a/src/Entity/UserApp.php +++ b/src/Entity/UserApp.php @@ -4,6 +4,7 @@ namespace App\Entity; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -13,6 +14,7 @@ use Symfony\Component\Security\Core\User\UserInterface; #[ORM\DiscriminatorColumn(name: 'DISCRIMINATOR', type: 'string')] #[ORM\DiscriminatorMap(['employee' => Employee::class, 'intern' => Intern::class])] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_NICKNAME', fields: ['nickname'])] +#[UniqueEntity(fields: ['nickname'], message: 'Il y a déjà un compte avec ces identifiants !')] class UserApp implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] @@ -26,8 +28,8 @@ class UserApp implements UserInterface, PasswordAuthenticatedUserInterface /** * @var list The user roles */ - #[ORM\Column(nullable: true)] - private ?array $roles = null; + #[ORM\Column] + private array $roles = []; /** * @var string The hashed password @@ -35,21 +37,24 @@ class UserApp implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?string $password = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255, nullable: true)] private ?string $firstName = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255, nullable: true)] private ?string $lastName = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255,nullable: true)] private ?string $tel = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255,nullable: true)] private ?string $address = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255,nullable: true)] private ?string $mail = null; + #[ORM\Column(nullable: true)] + private bool $isVerified = false; + public function getId(): ?int { return $this->id; @@ -184,6 +189,18 @@ class UserApp implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + public function isVerified(): bool + { + return $this->isVerified; + } + + public function setVerified(bool $isVerified): static + { + $this->isVerified = $isVerified; + + return $this; + } } diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 0000000..012084f --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,81 @@ +add('nickname', TextType::class, [ + 'label' => 'Utilisateur : ', + ]) + ->add('firstname', TextType::class, [ + 'label' => 'Prénom : ', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ] + ]) + ->add('lastname', TextType::class, [ + 'label' => 'Nom : ', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ] + ]) + ->add('mail', EmailType::class, [ + 'label' => 'Email : ', + 'required' => true, + 'constraints' => [ + new NotBlank(), + ] + ]) +// ->add('agreeTerms', CheckboxType::class, [ +// 'mapped' => false, +// 'constraints' => [ +// new IsTrue([ +// 'message' => 'Vous devez accepter les conditions d\'utilisation.', +// ]), +// ], +// ]) + ->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' => 'Merci d\'entrer votre mot de passe.', + ]), + new Length([ + 'min' => 6, + 'minMessage' => 'Votre mot de passe doit avoir au moins {{ limit }} caractères', + // max length allowed by Symfony for security reasons + 'max' => 4096, + ]), + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => UserApp::class, + ]); + } +} diff --git a/src/Security/EmailVerifier.php b/src/Security/EmailVerifier.php new file mode 100644 index 0000000..0768c22 --- /dev/null +++ b/src/Security/EmailVerifier.php @@ -0,0 +1,52 @@ +verifyEmailHelper->generateSignature( + $verifyEmailRouteName, + (string) $user->getId(), + (string) $user->getMail() + ); + + $context = $email->getContext(); + $context['signedUrl'] = $signatureComponents->getSignedUrl(); + $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); + $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); + + $email->context($context); + + $this->mailer->send($email); + } + + /** + * @throws VerifyEmailExceptionInterface + */ + public function handleEmailConfirmation(Request $request, UserApp $user): void + { + $this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user->getId(), (string) $user->getMail()); + + $user->setVerified(true); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} diff --git a/templates/announcement/list.html.twig b/templates/announcement/list.html.twig index 91cfb8d..ca4d140 100644 --- a/templates/announcement/list.html.twig +++ b/templates/announcement/list.html.twig @@ -14,6 +14,8 @@

{{ ann.company.name }}

{{ ann.description }}

------------------------------ + +

{{ ann.creationDate|format("") }}

{% endfor %} diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 0000000..38c6dc9 --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,22 @@ +{% extends 'base.html.twig' %} + +{% block title %}Inscription{% endblock %} + +{% block body %} +

Inscription

+ + {{ form_errors(registrationForm) }} + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.nickname) }} + {{ form_row(registrationForm.firstname) }} + {{ form_row(registrationForm.lastname) }} + {{ form_row(registrationForm.mail) }} + {{ form_row(registrationForm.plainPassword, { + label: 'Password' + }) }} +{# {{ form_row(registrationForm.agreeTerms) }}#} + + + {{ form_end(registrationForm) }} +{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..f711f6f --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,41 @@ +{% extends 'base.html.twig' %} + +{% block title %}Connexion{% endblock %} + +{% block body %} +
+ {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} + + {% if app.user %} +
+ You are logged in as {{ app.user.userIdentifier }}, Logout +
+ {% endif %} + +

Connexion à votre compte

+ + + + + + + + {# + Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. + See https://symfony.com/doc/current/security/remember_me.html + +
+ + +
+ #} + + +
+{% endblock %}