182 lines
7.9 KiB
PHP
182 lines
7.9 KiB
PHP
|
|
<?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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|