getClient('keycloak') ->redirect(['email', 'profile','openid'], []); } #[Route('/oauth/sso', name: 'connect_keycloak_check')] public function connectCheck(Request $request) { // This method stays empty; the authenticator will intercept it! } #[Route(path: '/', name: 'app_home', options: ['sitemap' => false], methods: ['GET','POST'])] public function index(AuthenticationUtils $authenticationUtils): Response { if($this->getUser()){ return $this->redirectToRoute('app_crm'); } 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); } } }