feat: Ajoute la fonctionnalité de création d'administrateur et envoi de mot de passe temporaire

Ce commit introduit une nouvelle fonctionnalité permettant de créer un compte administrateur via une commande console et d'envoyer un mot de passe temporaire par email.

Les changements incluent:

- Ajout d'une commande `AccountCommand` pour créer un compte administrateur.
- Création d'un service `TempPasswordGenerator` pour générer des mots de passe temporaires aléatoires.
- Ajout d'un événement `CreatedAdminEvent` pour déclencher l'envoi d'email après la création d'un administrateur.
- Modification du subscriber `MailerSubscriber` pour utiliser le nouveau template email et inclure le mot de passe temporaire.
- Création d'un nouveau template email `mails/artemis/new_admin.twig` pour l'envoi du mot de passe temporaire.
- Ajout de tests unitaires pour l'entité `Mail` et le repository `MailRepository`.
- Suppression de code commenté inutile dans `MailRepository`.
- Correction d'un bug dans `Mailer.php` pour passer les données au template twig.
- Mise à jour de la configuration `messenger.yaml` (suppression d'une ligne inutile).
This commit is contained in:
Serreau Jovann
2025-07-18 09:26:33 +02:00
parent c2767f2bd6
commit 742cded84a
10 changed files with 211 additions and 42 deletions

View File

@@ -5,4 +5,4 @@ framework:
async: "%env(MESSENGER_TRANSPORT_DSN)%"
routing:
'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

View File

@@ -3,12 +3,14 @@
namespace App\Command;
use App\Entity\Account;
use App\Service\Generator\TempPasswordGenerator;
use App\Service\Mailer\Event\CreatedAdminEvent;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Uid\Uuid;
@@ -29,14 +31,24 @@ class AccountCommand extends Command
$userExit = $this->entityManager->getRepository(Account::class)->findOneBy(['email'=>'jovann@siteconseil.fr']);
if(!$userExit instanceof Account) {
$password = TempPasswordGenerator::generate();
$userExit = new Account();
$userExit->setRoles(['ROLE_ROOT']);
$userExit->setUuid(Uuid::v4());
$userExit->setEmail("jovann@siteconseil.fr");
$userExit->setUsername("jovann");
$userExit->setPassword($this->userPasswordHasher->hashPassword($userExit, 'jovann'));
$this->eventDispatcher->dispatch(new CreatedAdminEvent($userExit,"jovann"));
$questionEmail = new Question("Email ?");
$email = $io->askQuestion($questionEmail);
$userExit->setEmail($email);
$questionUsername = new Question("Username ?");
$username = $io->askQuestion($questionUsername);
$userExit->setUsername($username);
$userExit->setPassword($this->userPasswordHasher->hashPassword($userExit, $password));
$this->entityManager->persist($userExit);
$this->entityManager->flush();
$this->eventDispatcher->dispatch(new CreatedAdminEvent($userExit, $password));
}
return Command::SUCCESS;

View File

@@ -14,9 +14,8 @@ class HomeController extends AbstractController
{
#[Route(path: '/',name: 'app_login',methods: ['GET', 'POST'])]
public function index(Mailer $mailer,AuthenticationUtils $authenticationUtils): Response
public function index(AuthenticationUtils $authenticationUtils): Response
{
$mailer->sendTest();
if ($this->getUser()) {
return $this->redirectToRoute('artemis_dashboard');
}

View File

@@ -15,29 +15,4 @@ class MailRepository extends ServiceEntityRepository
{
parent::__construct($registry, Mail::class);
}
// /**
// * @return Mail[] Returns an array of Mail objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('m')
// ->andWhere('m.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('m.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Mail
// {
// return $this->createQueryBuilder('m')
// ->andWhere('m.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Service\Generator;
/**
* Class TempPasswordGenerator
*
* Provides functionality to generate secure temporary passwords.
*/
class TempPasswordGenerator
{
/**
* Generates a random temporary password.
*
* @param int $length The desired length of the password. Default is 12 characters.
* @param string $characters A string of characters to use for password generation.
* Defaults to a mix of uppercase, lowercase, numbers, and symbols.
* @return string The generated temporary password.
*/
public static function generate(int $length = 12, string $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()-_=+[]{}|;:,.<>?') : string
{
// Ensure the length is positive
if ($length <= 0) {
// You might want to throw an exception or return an empty string
// depending on how you want to handle invalid lengths.
// For simplicity, we'll default to 12 if an invalid length is provided.
$length = 12;
}
$password = '';
$charactersLength = strlen($characters);
// Generate the password character by character
for ($i = 0; $i < $length; $i++) {
// Use random_int for cryptographically secure random number generation
$password .= $characters[random_int(0, $charactersLength - 1)];
}
return $password;
}
/**
* Checks if a password meets certain complexity requirements (optional).
* This is a basic example and can be extended.
*
* @param string $password The password to check.
* @return bool True if the password meets basic complexity, false otherwise.
*/
public static function isComplex(string $password): bool
{
// Minimum length
if (strlen($password) < 8) {
return false;
}
// Requires at least one uppercase letter
if (!preg_match('/[A-Z]/', $password)) {
return false;
}
// Requires at least one lowercase letter
if (!preg_match('/[a-z]/', $password)) {
return false;
}
// Requires at least one number
if (!preg_match('/[0-9]/', $password)) {
return false;
}
// Requires at least one special character
if (!preg_match('/[!@#$%^&*()-_=+\[\]{}|;:,.<>?]/', $password)) {
return false;
}
return true;
}
}

View File

@@ -98,16 +98,16 @@ class Mailer
$messageId = $mail->generateMessageId();
$header = $mail->getHeaders();
$header->add(new IdentificationHeader("Message-Id",$messageId));
$datas = $this->generateTracking($mail);
$datasSign = $this->generateTracking($mail);
/** @var Mail $object */
$object= $datas['object'];
$mjmlGenerator = $this->environment->render($template, array_merge([
$object = $datasSign['object'];
$mjmlGenerator = $this->environment->render($template, [
'system' => [
'subject' => $subject,
'tracking_url'=>$datas['url']
]
], $data));
'tracking_url'=>$datasSign['url']
],
'datas' => $data,
]);
$htmlContent = $this->convertMjmlToHtml($mjmlGenerator);
$object->setContent($htmlContent);
@@ -116,6 +116,7 @@ class Mailer
$this->mailer->send($mail);
$object->setStatus("sent");
} catch (TransportExceptionInterface $e) {
dd($e);
$object->setStatus("error");
}
$this->entityManager->persist($object);

View File

@@ -3,11 +3,12 @@ namespace App\Service\Mailer;
use App\Service\Mailer\Event\CreatedAdminEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
#[AsEventListener(event: CreatedAdminEvent::class, method: 'onAdminEvent')]
class MailerSubscriber
{
public function __construct(private readonly Mailer $mailer)
public function __construct(private readonly UrlGeneratorInterface $urlGenerator,private readonly Mailer $mailer)
{
}
@@ -16,10 +17,10 @@ class MailerSubscriber
$account = $createdAdminEvent->getAccount();
$password = $createdAdminEvent->getPassword();
dd($account, $password);
$this->mailer->send($account->getEmail(), $account->getUsername(), "[MainFrame] - Création d'un compte administrateur", "mails/artemis/new_admin.twig", [
'account' => $account,
'username' => $account->getUsername(),
'password' => $password,
'url' => $this->urlGenerator->generate('app_login',[],UrlGeneratorInterface::ABSOLUTE_URL)
]);
}
}

View File

@@ -0,0 +1,30 @@
{% extends 'mails/base.twig' %}
{% block content %}
<mj-text font-size="16px" line-height="24px">
Bonjour,
<br/><br/>
Nous avons le plaisir de vous informer que votre compte administrateur a été créé.
<br/><br/>
Voici vos identifiants de connexion temporaires :
<br/>
<strong>Nom d'utilisateur :</strong> `{{ datas.username }}`
<br/>
<strong>Mot de passe :</strong> `{{ datas.password }}`
<br/><br/>
Pour des raisons de sécurité, nous vous demandons de bien vouloir modifier votre mot de passe lors de votre première connexion.
<br/><br/>
Vous pouvez vous connecter à votre compte en cliquant sur le lien ci-dessous :
</mj-text>
<mj-button href="{{ datas.url }}" background-color="#4A90E2" color="#ffffff" font-size="16px" border-radius="5px">
Se connecter
</mj-button>
<mj-text font-size="16px" line-height="24px">
<br/><br/>
Si vous avez des questions ou rencontrez des difficultés, n'hésitez pas à nous contacter.
<br/><br/>
Cordialement,
<br/>
L'équipe Mainframe SITECONSEIL
</mj-text>
{% endblock %}

43
tests/Entity/MailTest.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
namespace App\Tests\Entity;
use App\Entity\Mail;
use PHPUnit\Framework\TestCase;
class MailTest extends TestCase
{
public function testMailEntity(): void
{
$mail = new Mail();
// Test messageId property
$messageId = 'test_message_id_123';
$mail->setMessageId($messageId);
$this->assertSame($messageId, $mail->getMessageId());
// Test status property
$status = 'sent';
$mail->setStatus($status);
$this->assertSame($status, $mail->getStatus());
// Test dest property
$dest = 'recipient@example.com';
$mail->setDest($dest);
$this->assertSame($dest, $mail->getDest());
// Test subject property
$subject = 'Test Subject';
$mail->setSubject($subject);
$this->assertSame($subject, $mail->getSubject());
// Test content property
$content = 'This is the test email content.';
$mail->setContent($content);
$this->assertSame($content, $mail->getContent());
// Test getId() - should be null initially as it's auto-generated by the database
$this->assertNull($mail->getId());
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Tests\Repository;
use App\Entity\Mail;
use App\Repository\MailRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Doctrine\ORM\EntityManagerInterface;
class MailRepositoryTest extends KernelTestCase
{
private ?EntityManagerInterface $entityManager;
private ?MailRepository $mailRepository;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = self::getContainer()->get('doctrine')->getManager();
$this->mailRepository = $this->entityManager->getRepository(Mail::class);
}
public function testRepositoryExistsAndIsCorrectInstance(): void
{
$this->assertInstanceOf(MailRepository::class, $this->mailRepository);
}
protected function tearDown(): void
{
parent::tearDown();
$this->entityManager->close();
$this->entityManager = null; // Avoid memory leaks
$this->mailRepository = null;
}
}