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

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