src/Controller/Front/Account/AccountController.php line 152

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Front\Account;
  3. use App\Application\Service\Authentication\MultifactorAuthService;
  4. use App\Application\Service\Helper\LinkGenerator;
  5. use App\Application\Service\Helper\ToolsService;
  6. use App\Application\Service\Session\SessionService;
  7. use App\Client\Microsoft\Service\TokenService;
  8. use App\Client\Microsoft\Service\UserService;
  9. use App\Entity\System\Customer;
  10. use App\Entity\System\Employee;
  11. use App\Manager\System\CustomerManager;
  12. use App\Manager\System\TokenManager;
  13. use App\Service\AuthenticatedSessionService;
  14. use App\Service\CustomerInformationUpdate\CustomerInformationUpdateService;
  15. use App\Service\EnvironmentService;
  16. use App\Service\EventDispatcher;
  17. use App\ViewManager\Landing\LandingService;
  18. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  19. use Symfony\Component\HttpFoundation\Cookie;
  20. use Symfony\Component\HttpFoundation\JsonResponse;
  21. use Symfony\Component\HttpFoundation\RedirectResponse;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\RequestStack;
  24. use Symfony\Component\HttpFoundation\Response;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  27. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  28. use Symfony\Component\Security\Http\Event\LogoutEvent;
  29. use Symfony\Component\Uid\Uuid;
  30. use Symfony\Contracts\Translation\TranslatorInterface;
  31. use function Sentry\captureException;
  32. use function Sentry\captureMessage;
  33. class AccountController extends AbstractController
  34. {
  35. public const URL_FORGOT_PASSWORD_TOKEN_EXPIRED = 'forgot-password-token-expired?token=';
  36. public const URL_UPDATE_PASSWORD = 'update-password?token=';
  37. private CustomerManager $customerManager;
  38. private MultifactorAuthService $multifactorAuthService;
  39. private AuthenticatedSessionService $authenticatedSessionService;
  40. private TranslatorInterface $translator;
  41. private TokenStorageInterface $tokenStorage;
  42. private LandingService $landingService;
  43. private LinkGenerator $linkGenerator;
  44. private SessionService $sessionService;
  45. private TokenManager $tokenManager;
  46. private ToolsService $toolsService;
  47. private TokenService $tokenService;
  48. private UserService $userService;
  49. private EnvironmentService $environmentService;
  50. private CustomerInformationUpdateService $customerInformationUpdateService;
  51. private RequestStack $requestStack;
  52. private EventDispatcher $eventDispatcher;
  53. public function __construct(
  54. CustomerManager $customerManager,
  55. MultifactorAuthService $multifactorAuthService,
  56. AuthenticatedSessionService $authenticatedSessionService,
  57. TranslatorInterface $translator,
  58. TokenStorageInterface $tokenStorage,
  59. LandingService $landingService,
  60. LinkGenerator $linkGenerator,
  61. SessionService $sessionService,
  62. TokenManager $tokenManager,
  63. ToolsService $toolsService,
  64. TokenService $tokenService,
  65. UserService $userService,
  66. EnvironmentService $environmentService,
  67. CustomerInformationUpdateService $customerInformationUpdateService,
  68. RequestStack $requestStack,
  69. EventDispatcher $eventDispatcher
  70. ) {
  71. $this->customerManager = $customerManager;
  72. $this->multifactorAuthService = $multifactorAuthService;
  73. $this->authenticatedSessionService = $authenticatedSessionService;
  74. $this->translator = $translator;
  75. $this->tokenStorage = $tokenStorage;
  76. $this->landingService = $landingService;
  77. $this->linkGenerator = $linkGenerator;
  78. $this->sessionService = $sessionService;
  79. $this->tokenManager = $tokenManager;
  80. $this->toolsService = $toolsService;
  81. $this->tokenService = $tokenService;
  82. $this->userService = $userService;
  83. $this->environmentService = $environmentService;
  84. $this->customerInformationUpdateService = $customerInformationUpdateService;
  85. $this->requestStack = $requestStack;
  86. $this->eventDispatcher = $eventDispatcher;
  87. }
  88. /**
  89. * @Route("/login", name="customer_login")
  90. */
  91. public function login(): Response
  92. {
  93. return $this->redirect($this->linkGenerator->getFrontUrl('login'));
  94. }
  95. /**
  96. * @Route("/{lang}/account/popuplogin", name="customer_popuplogin_lang", requirements={"lang"="[a-zA-Z]{2}"})
  97. * @Route("/account/popuplogin", name="customer_popuplogin")
  98. */
  99. public function loginPopUp(Request $request): Response
  100. {
  101. $user = $this->getUser();
  102. if ($request->isXmlHttpRequest()) {
  103. if ($user) {
  104. return new Response('Already logged');
  105. }
  106. return $this->render(
  107. 'front/account/'.($this->toolsService::isMobile() ? 'mobile_login.html.twig' : 'login.html.twig'),
  108. );
  109. }
  110. if (!$user) {
  111. $sfRedirect = json_decode($request->cookies->get('sf_redirect'), true);
  112. if (str_contains($request->headers->get('referer'), '/admin')
  113. || (isset($sfRedirect['route']) && str_contains($sfRedirect['route'], 'admin'))) {
  114. return $this->redirectToRoute('admin_login');
  115. }
  116. if ($this->environmentService->isProd()) {
  117. return $this->redirect($this->linkGenerator->getFrontUrl('login'));
  118. }
  119. return $this->render(
  120. 'front/account/base_login.html.twig',
  121. [
  122. 'landingHtml' => $this->landingService->getLandingContentInternal('bigbuyLogin'),
  123. ]
  124. );
  125. }
  126. if ($user instanceof Employee) {
  127. return $this->redirectToRoute('admin_dashboard_index');
  128. }
  129. return $this->redirectToRoute('homepage');
  130. }
  131. /**
  132. * @Route("/{lang}/account/logout", name="customer_logout_lang", requirements={"lang"="[a-zA-Z]{2}"})
  133. * @Route("/account/logout", name="customer_logout_customized")
  134. */
  135. public function logout(Request $request): Response
  136. {
  137. $user = $this->getUser();
  138. if (!$user) {
  139. return $this->redirectToRoute('homepage');
  140. }
  141. if ($user instanceof Customer) {
  142. $refererUrl = $request->headers->get('referer');
  143. $this->authenticatedSessionService->logoutLegacy();
  144. $this->tokenStorage->setToken();
  145. if ($refererUrl !== null) {
  146. return $this->redirect($refererUrl);
  147. }
  148. return $this->redirectToRoute('homepage');
  149. }
  150. $this->tokenStorage->setToken();
  151. $this->requestStack->getSession()->invalidate();
  152. return $this->redirectToRoute('admin_login');
  153. }
  154. /**
  155. * @Route("/{lang}/account/multifactor-auth", name="customer_confirm_auth", requirements={"lang"="[a-zA-Z]{2}"})
  156. */
  157. public function confirmAuth(Request $request): Response
  158. {
  159. if ($request->getMethod() === Request::METHOD_GET) {
  160. return $this->render(
  161. 'front/account/confirm_auth.html.twig',
  162. [
  163. 'email' => $request->get('email'),
  164. ]
  165. );
  166. }
  167. $code = trim($request->request->get('authcode'));
  168. $email = $request->request->get('email');
  169. $user = $this->customerManager->findOneBy(['email' => $email]);
  170. $multifactorAuth = $this->multifactorAuthService->findValidMultifactorAuth($user, $code);
  171. if (!$multifactorAuth) {
  172. return new JsonResponse(
  173. ['isLogged' => false, 'errors' => ['authcode' => $this->translator->trans('mfa.error.wrong_code')]]
  174. );
  175. }
  176. $this->authenticatedSessionService->loadCustomerSession($user);
  177. $this->authenticatedSessionService->authenticateCustomer($user);
  178. [$cookieHash, $cookieExpirationTimestamp] = $this->multifactorAuthService->updateMultifactorAuthWithCookieData(
  179. $user,
  180. $multifactorAuth
  181. );
  182. $request->getSession()->remove(MultifactorAuthService::SESSION_EMAIL_CUSTOMER_KEY);
  183. $response = new JsonResponse(['isLogged' => true, 'redirect' => $this->sessionService->get('referer')]);
  184. $browserCookie = Cookie::create(
  185. MultifactorAuthService::MULTIFACTOR_AUTH_CUSTOMER_COOKIE_NAME,
  186. $cookieHash,
  187. $cookieExpirationTimestamp
  188. );
  189. $response->headers->setCookie($browserCookie);
  190. return $response;
  191. }
  192. /**
  193. * @Route("/account/resend-mfa-code", name="multifactor_auth_resend_code", requirements={"lang"="[a-zA-Z]{2}"})
  194. */
  195. public function resendMultifactorAuthCode(Request $request): Response
  196. {
  197. $email = $request->getSession()->get(MultifactorAuthService::SESSION_EMAIL_CUSTOMER_KEY);
  198. $customer = $this->customerManager->findOneBy(['email' => $email]);
  199. try {
  200. $this->multifactorAuthService->sendAuthCodeToUser($customer);
  201. } catch (\Throwable $t) {
  202. return new JsonResponse(['error' => 'Something went wrong']);
  203. }
  204. return new JsonResponse(['message' => $this->translator->trans('mfa.popup.code_sent')]);
  205. }
  206. /**
  207. * @Route("/{lang}/account/update-password/{updatePasswordToken}", name="app_front_account_update_password", requirements={"lang"="[a-zA-Z]{2}", "updatePasswordToken"="[a-zA-Z0-9]+"})
  208. */
  209. public function updatePassword(?string $updatePasswordToken = null): Response
  210. {
  211. $localeId = $this->sessionService->getLocaleId();
  212. if (empty($updatePasswordToken)) {
  213. return $this->redirect(
  214. $this->linkGenerator->getFrontUrl(
  215. self::URL_FORGOT_PASSWORD_TOKEN_EXPIRED.$updatePasswordToken,
  216. $localeId
  217. )
  218. );
  219. }
  220. $token = $this->tokenManager->findOneByToken($updatePasswordToken);
  221. if ($token === null || $token->isUsed()) {
  222. return $this->redirect(
  223. $this->linkGenerator->getFrontUrl(
  224. self::URL_FORGOT_PASSWORD_TOKEN_EXPIRED.$updatePasswordToken,
  225. $localeId
  226. )
  227. );
  228. }
  229. return $this->redirect(
  230. $this->linkGenerator->getFrontUrl('update-password?token='.$updatePasswordToken, $localeId)
  231. );
  232. }
  233. /**
  234. * @Route("/account/connect/microsoft", name="microsoft_login")
  235. */
  236. public function microsoftLogin(Request $request): RedirectResponse
  237. {
  238. if ($this->getUser()) {
  239. return $this->redirectToRoute('homepage');
  240. }
  241. $redirectUri = $this->generateUrl('microsoft_callback', [], UrlGeneratorInterface::ABSOLUTE_URL);
  242. $state = Uuid::v4()->toRfc4122();
  243. $request->getSession()->set('oauth_state', $state);
  244. return new RedirectResponse($this->tokenService->generateAuthorizationUrl($state, $redirectUri));
  245. }
  246. /**
  247. * @Route("/connect/microsoft/check", name="microsoft_callback")
  248. */
  249. public function microsoftCallback(Request $request): RedirectResponse
  250. {
  251. $code = $request->query->get('code');
  252. $state = $request->query->get('state');
  253. $storedState = $request->getSession()->get('oauth_state');
  254. if ($state !== $storedState || empty($state)) {
  255. $this->addFlash('danger', 'Invalid state parameter.');
  256. return $this->redirectToRoute('admin_login');
  257. }
  258. if (!$code) {
  259. $this->addFlash('danger', 'Authentication failed: No code received.');
  260. return $this->redirectToRoute('admin_login');
  261. }
  262. $redirectUri = $this->generateUrl('microsoft_callback', [], UrlGeneratorInterface::ABSOLUTE_URL);
  263. try {
  264. $token = $this->tokenService->getTokenWithCode($redirectUri, $code);
  265. $microsoftUser = $this->userService->getMe($token->getAccessToken());
  266. } catch (\Exception $exception) {
  267. $this->addFlash('danger', 'Authentication failed: '.$exception->getMessage());
  268. captureException($exception);
  269. return $this->redirectToRoute('admin_login');
  270. }
  271. $user = $this->customerManager->findOneBy(['email' => $microsoftUser->getMail()]);
  272. if (!$user) {
  273. $this->addFlash('danger', 'Authentication failed: User not found.');
  274. captureMessage('Microsoft user not found with email: '.$microsoftUser->getMail());
  275. return $this->redirectToRoute('admin_login');
  276. }
  277. if (!$user->isActive()) {
  278. $this->addFlash('danger', 'Authentication failed: User disabled.');
  279. return $this->redirectToRoute('admin_login');
  280. }
  281. $this->customerInformationUpdateService->updateCustomerEmployee($user, $microsoftUser);
  282. $this->authenticatedSessionService->authenticateCustomer($user);
  283. return $this->redirectToRoute('homepage');
  284. }
  285. /**
  286. * @Route("/change-to-employee", name="admin_change_to_employee")
  287. */
  288. public function changeToEmployee(): Response
  289. {
  290. $logoutEvent = new LogoutEvent($this->requestStack->getCurrentRequest(), $this->tokenStorage->getToken());
  291. $this->eventDispatcher->dispatch($logoutEvent);
  292. $this->tokenStorage->setToken();
  293. $this->requestStack->getSession()->invalidate();
  294. return $this->redirectToRoute('admin_microsoft_login');
  295. }
  296. /**
  297. * @Route("/microsoft/user/change", name="microsoft_user_change_notification")
  298. */
  299. public function microsoftUserChangeNotification(Request $request): Response
  300. {
  301. $validationToken = $request->query->get('validationToken');
  302. if ($validationToken) {
  303. return new Response($validationToken, Response::HTTP_OK, ['Content-Type' => 'text/plain']);
  304. }
  305. $notifications = json_decode($request->getContent(), true);
  306. $userId = null;
  307. foreach ($notifications['value'] as $value) {
  308. if ($value['resourceData']['@odata.type'] === '#Microsoft.Graph.User') {
  309. $userId = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
  310. break;
  311. }
  312. }
  313. if (!$userId) {
  314. return new Response(null, Response::HTTP_ACCEPTED);
  315. }
  316. try {
  317. $token = $this->tokenService->getToken();
  318. $microsoftUser = $this->userService->getUserByIdOrEmail($userId, $token->getAccessToken());
  319. } catch (\Exception $exception) {
  320. $this->addFlash('danger', 'Authentication failed: '.$exception->getMessage());
  321. captureException($exception);
  322. return new Response(null, Response::HTTP_NOT_ACCEPTABLE);
  323. }
  324. if (!$microsoftUser->isAccountEnabled()) {
  325. $customer = $this->customerManager->findOneByEmail($microsoftUser->getMail());
  326. if ($customer) {
  327. $customer->setActive(false);
  328. $this->customerManager->save($customer);
  329. }
  330. }
  331. return new Response(null, Response::HTTP_ACCEPTED);
  332. }
  333. }