<?php
namespace App\Controller\Front\Account;
use App\Application\Service\Authentication\MultifactorAuthService;
use App\Application\Service\Helper\LinkGenerator;
use App\Application\Service\Helper\ToolsService;
use App\Application\Service\Session\SessionService;
use App\Client\Microsoft\Service\TokenService;
use App\Client\Microsoft\Service\UserService;
use App\Entity\System\Customer;
use App\Entity\System\Employee;
use App\Manager\System\CustomerManager;
use App\Manager\System\TokenManager;
use App\Service\AuthenticatedSessionService;
use App\Service\CustomerInformationUpdate\CustomerInformationUpdateService;
use App\Service\EnvironmentService;
use App\Service\EventDispatcher;
use App\ViewManager\Landing\LandingService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Component\Uid\Uuid;
use Symfony\Contracts\Translation\TranslatorInterface;
use function Sentry\captureException;
use function Sentry\captureMessage;
class AccountController extends AbstractController
{
public const URL_FORGOT_PASSWORD_TOKEN_EXPIRED = 'forgot-password-token-expired?token=';
public const URL_UPDATE_PASSWORD = 'update-password?token=';
private CustomerManager $customerManager;
private MultifactorAuthService $multifactorAuthService;
private AuthenticatedSessionService $authenticatedSessionService;
private TranslatorInterface $translator;
private TokenStorageInterface $tokenStorage;
private LandingService $landingService;
private LinkGenerator $linkGenerator;
private SessionService $sessionService;
private TokenManager $tokenManager;
private ToolsService $toolsService;
private TokenService $tokenService;
private UserService $userService;
private EnvironmentService $environmentService;
private CustomerInformationUpdateService $customerInformationUpdateService;
private RequestStack $requestStack;
private EventDispatcher $eventDispatcher;
public function __construct(
CustomerManager $customerManager,
MultifactorAuthService $multifactorAuthService,
AuthenticatedSessionService $authenticatedSessionService,
TranslatorInterface $translator,
TokenStorageInterface $tokenStorage,
LandingService $landingService,
LinkGenerator $linkGenerator,
SessionService $sessionService,
TokenManager $tokenManager,
ToolsService $toolsService,
TokenService $tokenService,
UserService $userService,
EnvironmentService $environmentService,
CustomerInformationUpdateService $customerInformationUpdateService,
RequestStack $requestStack,
EventDispatcher $eventDispatcher
) {
$this->customerManager = $customerManager;
$this->multifactorAuthService = $multifactorAuthService;
$this->authenticatedSessionService = $authenticatedSessionService;
$this->translator = $translator;
$this->tokenStorage = $tokenStorage;
$this->landingService = $landingService;
$this->linkGenerator = $linkGenerator;
$this->sessionService = $sessionService;
$this->tokenManager = $tokenManager;
$this->toolsService = $toolsService;
$this->tokenService = $tokenService;
$this->userService = $userService;
$this->environmentService = $environmentService;
$this->customerInformationUpdateService = $customerInformationUpdateService;
$this->requestStack = $requestStack;
$this->eventDispatcher = $eventDispatcher;
}
/**
* @Route("/login", name="customer_login")
*/
public function login(): Response
{
return $this->redirect($this->linkGenerator->getFrontUrl('login'));
}
/**
* @Route("/{lang}/account/popuplogin", name="customer_popuplogin_lang", requirements={"lang"="[a-zA-Z]{2}"})
* @Route("/account/popuplogin", name="customer_popuplogin")
*/
public function loginPopUp(Request $request): Response
{
$user = $this->getUser();
if ($request->isXmlHttpRequest()) {
if ($user) {
return new Response('Already logged');
}
return $this->render(
'front/account/'.($this->toolsService::isMobile() ? 'mobile_login.html.twig' : 'login.html.twig'),
);
}
if (!$user) {
$sfRedirect = json_decode($request->cookies->get('sf_redirect'), true);
if (str_contains($request->headers->get('referer'), '/admin')
|| (isset($sfRedirect['route']) && str_contains($sfRedirect['route'], 'admin'))) {
return $this->redirectToRoute('admin_login');
}
if ($this->environmentService->isProd()) {
return $this->redirect($this->linkGenerator->getFrontUrl('login'));
}
return $this->render(
'front/account/base_login.html.twig',
[
'landingHtml' => $this->landingService->getLandingContentInternal('bigbuyLogin'),
]
);
}
if ($user instanceof Employee) {
return $this->redirectToRoute('admin_dashboard_index');
}
return $this->redirectToRoute('homepage');
}
/**
* @Route("/{lang}/account/logout", name="customer_logout_lang", requirements={"lang"="[a-zA-Z]{2}"})
* @Route("/account/logout", name="customer_logout_customized")
*/
public function logout(Request $request): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('homepage');
}
if ($user instanceof Customer) {
$refererUrl = $request->headers->get('referer');
$this->authenticatedSessionService->logoutLegacy();
$this->tokenStorage->setToken();
if ($refererUrl !== null) {
return $this->redirect($refererUrl);
}
return $this->redirectToRoute('homepage');
}
$this->tokenStorage->setToken();
$this->requestStack->getSession()->invalidate();
return $this->redirectToRoute('admin_login');
}
/**
* @Route("/{lang}/account/multifactor-auth", name="customer_confirm_auth", requirements={"lang"="[a-zA-Z]{2}"})
*/
public function confirmAuth(Request $request): Response
{
if ($request->getMethod() === Request::METHOD_GET) {
return $this->render(
'front/account/confirm_auth.html.twig',
[
'email' => $request->get('email'),
]
);
}
$code = trim($request->request->get('authcode'));
$email = $request->request->get('email');
$user = $this->customerManager->findOneBy(['email' => $email]);
$multifactorAuth = $this->multifactorAuthService->findValidMultifactorAuth($user, $code);
if (!$multifactorAuth) {
return new JsonResponse(
['isLogged' => false, 'errors' => ['authcode' => $this->translator->trans('mfa.error.wrong_code')]]
);
}
$this->authenticatedSessionService->loadCustomerSession($user);
$this->authenticatedSessionService->authenticateCustomer($user);
[$cookieHash, $cookieExpirationTimestamp] = $this->multifactorAuthService->updateMultifactorAuthWithCookieData(
$user,
$multifactorAuth
);
$request->getSession()->remove(MultifactorAuthService::SESSION_EMAIL_CUSTOMER_KEY);
$response = new JsonResponse(['isLogged' => true, 'redirect' => $this->sessionService->get('referer')]);
$browserCookie = Cookie::create(
MultifactorAuthService::MULTIFACTOR_AUTH_CUSTOMER_COOKIE_NAME,
$cookieHash,
$cookieExpirationTimestamp
);
$response->headers->setCookie($browserCookie);
return $response;
}
/**
* @Route("/account/resend-mfa-code", name="multifactor_auth_resend_code", requirements={"lang"="[a-zA-Z]{2}"})
*/
public function resendMultifactorAuthCode(Request $request): Response
{
$email = $request->getSession()->get(MultifactorAuthService::SESSION_EMAIL_CUSTOMER_KEY);
$customer = $this->customerManager->findOneBy(['email' => $email]);
try {
$this->multifactorAuthService->sendAuthCodeToUser($customer);
} catch (\Throwable $t) {
return new JsonResponse(['error' => 'Something went wrong']);
}
return new JsonResponse(['message' => $this->translator->trans('mfa.popup.code_sent')]);
}
/**
* @Route("/{lang}/account/update-password/{updatePasswordToken}", name="app_front_account_update_password", requirements={"lang"="[a-zA-Z]{2}", "updatePasswordToken"="[a-zA-Z0-9]+"})
*/
public function updatePassword(?string $updatePasswordToken = null): Response
{
$localeId = $this->sessionService->getLocaleId();
if (empty($updatePasswordToken)) {
return $this->redirect(
$this->linkGenerator->getFrontUrl(
self::URL_FORGOT_PASSWORD_TOKEN_EXPIRED.$updatePasswordToken,
$localeId
)
);
}
$token = $this->tokenManager->findOneByToken($updatePasswordToken);
if ($token === null || $token->isUsed()) {
return $this->redirect(
$this->linkGenerator->getFrontUrl(
self::URL_FORGOT_PASSWORD_TOKEN_EXPIRED.$updatePasswordToken,
$localeId
)
);
}
return $this->redirect(
$this->linkGenerator->getFrontUrl('update-password?token='.$updatePasswordToken, $localeId)
);
}
/**
* @Route("/account/connect/microsoft", name="microsoft_login")
*/
public function microsoftLogin(Request $request): RedirectResponse
{
if ($this->getUser()) {
return $this->redirectToRoute('homepage');
}
$redirectUri = $this->generateUrl('microsoft_callback', [], UrlGeneratorInterface::ABSOLUTE_URL);
$state = Uuid::v4()->toRfc4122();
$request->getSession()->set('oauth_state', $state);
return new RedirectResponse($this->tokenService->generateAuthorizationUrl($state, $redirectUri));
}
/**
* @Route("/connect/microsoft/check", name="microsoft_callback")
*/
public function microsoftCallback(Request $request): RedirectResponse
{
$code = $request->query->get('code');
$state = $request->query->get('state');
$storedState = $request->getSession()->get('oauth_state');
if ($state !== $storedState || empty($state)) {
$this->addFlash('danger', 'Invalid state parameter.');
return $this->redirectToRoute('admin_login');
}
if (!$code) {
$this->addFlash('danger', 'Authentication failed: No code received.');
return $this->redirectToRoute('admin_login');
}
$redirectUri = $this->generateUrl('microsoft_callback', [], UrlGeneratorInterface::ABSOLUTE_URL);
try {
$token = $this->tokenService->getTokenWithCode($redirectUri, $code);
$microsoftUser = $this->userService->getMe($token->getAccessToken());
} catch (\Exception $exception) {
$this->addFlash('danger', 'Authentication failed: '.$exception->getMessage());
captureException($exception);
return $this->redirectToRoute('admin_login');
}
$user = $this->customerManager->findOneBy(['email' => $microsoftUser->getMail()]);
if (!$user) {
$this->addFlash('danger', 'Authentication failed: User not found.');
captureMessage('Microsoft user not found with email: '.$microsoftUser->getMail());
return $this->redirectToRoute('admin_login');
}
if (!$user->isActive()) {
$this->addFlash('danger', 'Authentication failed: User disabled.');
return $this->redirectToRoute('admin_login');
}
$this->customerInformationUpdateService->updateCustomerEmployee($user, $microsoftUser);
$this->authenticatedSessionService->authenticateCustomer($user);
return $this->redirectToRoute('homepage');
}
/**
* @Route("/change-to-employee", name="admin_change_to_employee")
*/
public function changeToEmployee(): Response
{
$logoutEvent = new LogoutEvent($this->requestStack->getCurrentRequest(), $this->tokenStorage->getToken());
$this->eventDispatcher->dispatch($logoutEvent);
$this->tokenStorage->setToken();
$this->requestStack->getSession()->invalidate();
return $this->redirectToRoute('admin_microsoft_login');
}
/**
* @Route("/microsoft/user/change", name="microsoft_user_change_notification")
*/
public function microsoftUserChangeNotification(Request $request): Response
{
$validationToken = $request->query->get('validationToken');
if ($validationToken) {
return new Response($validationToken, Response::HTTP_OK, ['Content-Type' => 'text/plain']);
}
$notifications = json_decode($request->getContent(), true);
$userId = null;
foreach ($notifications['value'] as $value) {
if ($value['resourceData']['@odata.type'] === '#Microsoft.Graph.User') {
$userId = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
break;
}
}
if (!$userId) {
return new Response(null, Response::HTTP_ACCEPTED);
}
try {
$token = $this->tokenService->getToken();
$microsoftUser = $this->userService->getUserByIdOrEmail($userId, $token->getAccessToken());
} catch (\Exception $exception) {
$this->addFlash('danger', 'Authentication failed: '.$exception->getMessage());
captureException($exception);
return new Response(null, Response::HTTP_NOT_ACCEPTABLE);
}
if (!$microsoftUser->isAccountEnabled()) {
$customer = $this->customerManager->findOneByEmail($microsoftUser->getMail());
if ($customer) {
$customer->setActive(false);
$this->customerManager->save($customer);
}
}
return new Response(null, Response::HTTP_ACCEPTED);
}
}