```
[+] chore(root): Initialise le projet avec une structure de base Crée la structure de base du projet Symfony, incluant les entités, services, formulaires, et templates nécessaires pour la gestion des comptes utilisateurs, la sécurité, et la gestion des mots de passe oubliés. Ajoute également la configuration pour la gestion des assets avec Vite, la gestion des fichiers avec Flysystem, et la génération de sitemaps. ```
This commit is contained in:
181
src/Controller/HomeController.php
Normal file
181
src/Controller/HomeController.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/', name: 'app_home', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function index(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
return $this->render('home.twig',[
|
||||
'last_username' => $authenticationUtils->getLastUsername(),
|
||||
'error' => $authenticationUtils->getLastAuthenticationError(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/logout', name: 'app_logout', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function logout(): Response
|
||||
{
|
||||
|
||||
}
|
||||
#[Route(path: '/mot-de-passe-oublie', name: 'app_forgot_password', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function forgotPassword(Request $request,EventDispatcherInterface $eventDispatcher): Response
|
||||
{
|
||||
$requestPasswordRequest = new ResetPasswordEvent();
|
||||
$form = $this->createForm(RequestPasswordRequestType::class,$requestPasswordRequest);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$eventDispatcher->dispatch($requestPasswordRequest);
|
||||
return $this->redirectToRoute('app_forgot_password_sent');
|
||||
}
|
||||
return $this->render('security/forgot_password.twig', [
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/mot-de-passe-oublie/sent', name: 'app_forgot_password_sent', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function forgotPasswordSent(Request $request,EventDispatcherInterface $eventDispatcher): Response
|
||||
{
|
||||
return $this->render('security/forgot_password_success.twig', [
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/mot-de-passe-oublie/{id}/{token}', name: 'app_forgot_password_confirm', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function forgotPasswordConfirm(UserPasswordHasherInterface $userPasswordHasher,EventDispatcherInterface $eventDispatcher,Request $request,EntityManagerInterface $entityManager,string $id,string $token): Response
|
||||
{
|
||||
$errorMessage = "Requête non valide.";
|
||||
if (!is_numeric($id)) {
|
||||
$this->addFlash("error", $errorMessage);
|
||||
return $this->redirectToRoute('app_forgot_password');
|
||||
}
|
||||
$account = $entityManager->getRepository(Account::class)->find((int)$id);
|
||||
if (!$account instanceof Account) {
|
||||
$this->addFlash("error", $errorMessage);
|
||||
return $this->redirectToRoute('app_forgot_password');
|
||||
}
|
||||
$requestToken = $entityManager->getRepository(AccountResetPasswordRequest::class)->findOneBy([
|
||||
'Account' => $account, // Assurez-vous que 'Account' est le nom correct de la propriété/colonne dans votre entité AccountResetPasswordRequest.
|
||||
'token' => $token
|
||||
]);
|
||||
if (!$requestToken instanceof AccountResetPasswordRequest) {
|
||||
$this->addFlash("error", $errorMessage);
|
||||
return $this->redirectToRoute('app_forgot_password');
|
||||
}
|
||||
$now = new \DateTimeImmutable();
|
||||
if ($requestToken->getExpiresAt() < $now) {
|
||||
$this->addFlash("error", "Le lien de réinitialisation de mot de passe a expiré.");
|
||||
return $this->redirectToRoute('app_forgot_password');
|
||||
}
|
||||
|
||||
$event = new ResetPasswordConfirmEvent();
|
||||
$form = $this->createForm(RequestPasswordConfirmType::class,$event);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$account->setPassword($userPasswordHasher->hashPassword($account,$event->getPassword()));
|
||||
$entityManager->persist($account);
|
||||
$entityManager->flush();
|
||||
$this->addFlash("success", "Votre mot de passe a été mis à jour avec succès.");
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
return $this->render('security/forgot-password-confirm.twig', [
|
||||
'form' => $form->createView(),
|
||||
'noIndex' => true,
|
||||
'id' => $id,
|
||||
'token' => $token,
|
||||
'account' => $account,
|
||||
]);
|
||||
}
|
||||
const SENTRY_HOST = '';
|
||||
const SENTRY_PROJECT_IDS = [''];
|
||||
|
||||
#[Route('/uptime',name: 'app_uptime',options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function app_uptime(Request $request,HttpClientInterface $httpClient): Response
|
||||
{
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
#[Route('/tunnel',name: 'app_tunnel',options: ['sitemap' => false], methods: ['POST'])]
|
||||
public function tunnel(Request $request,HttpClientInterface $httpClient): Response
|
||||
{
|
||||
$envelope = $request->getContent();
|
||||
if (empty($envelope)) {
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. Extract the header piece (first line)
|
||||
$pieces = explode("\n", $envelope, 2);
|
||||
$piece = $pieces[0];
|
||||
|
||||
// 3. Parse the header (which is JSON)
|
||||
$header = json_decode($piece, true);
|
||||
|
||||
if (!isset($header['dsn'])) {
|
||||
throw new \Exception("Missing DSN in envelope header.");
|
||||
}
|
||||
|
||||
// 4. Extract and validate DSN and Project ID
|
||||
$dsnUrl = parse_url($header['dsn']);
|
||||
$dsnHostname = $dsnUrl['host'] ?? null;
|
||||
$dsnPath = $dsnUrl['path'] ?? '/';
|
||||
|
||||
// Remove leading/trailing slashes from the path to get the project_id
|
||||
$projectId = trim($dsnPath, '/');
|
||||
|
||||
|
||||
if ($dsnHostname !== self::SENTRY_HOST) {
|
||||
throw new \Exception("Invalid sentry hostname: {$dsnHostname}");
|
||||
}
|
||||
|
||||
if (empty($projectId) || !in_array($projectId, self::SENTRY_PROJECT_IDS)) {
|
||||
throw new \Exception("Invalid sentry project id: {$projectId}");
|
||||
}
|
||||
|
||||
// 5. Construct the upstream Sentry URL
|
||||
$upstreamSentryUrl = "https://" . self::SENTRY_HOST . "/api/" . $projectId . "/envelope/";
|
||||
|
||||
// 6. Forward the request using an HTTP client (e.g., Guzzle)
|
||||
|
||||
$response = $httpClient->request("POST",$upstreamSentryUrl, [
|
||||
'body' => $envelope,
|
||||
'headers' => [
|
||||
// Sentry expects this content type
|
||||
'Content-Type' => 'application/x-sentry-envelope',
|
||||
// Forward the content encoding if present, though often not needed
|
||||
// 'Content-Encoding' => $request->headers->get('Content-Encoding'),
|
||||
],
|
||||
]);
|
||||
|
||||
// 7. Return the status from the upstream Sentry response
|
||||
return new JsonResponse([], $response->getStatusCode());
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Log the error for server-side debugging
|
||||
error_log("Error tunneling to Sentry: " . $e->getMessage());
|
||||
|
||||
// Return a success status (200/202) or a non-specific 500 to the client.
|
||||
// Returning a non-error status (like 200) is often preferred for tunnels
|
||||
// to avoid triggering ad-blockers on failures.
|
||||
return new JsonResponse([
|
||||
'error' => 'An error occurred during tunneling.'
|
||||
], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user