<?php
namespace App\Controller;
use App\Entity\CustomerDetail;
use App\Entity\DeviceKey;
use App\Entity\PageDesign;
use App\Entity\Plan;
use App\Entity\ResetPassword;
use App\Entity\User;
use App\Entity\Workspace;
use App\Form\RegisterUserType;
use App\Repository\PageDesignRepository;
use App\Repository\ResetPasswordRepository;
use App\Repository\UserRepository;
use App\Security\LoginFormAuthenticator;
use App\Service\MailService;
use App\Service\UserService;
use App\Utils\CommonFunctions;
use App\Utils\Commons;
use App\Utils\UserValidator;
use DateTime;
use DateTimeZone;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Exception;
use ReCaptcha\ReCaptcha;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Form\FormError;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
class SecurityController extends AbstractController
{
/**
* Frontend Login Page
* Accepts symfony's built-in guard authentication provider to authenticate user
* which is configured in security.yaml under firewall.
* Throws authenticationUtils error if user does not exists.
* Check LoginFormAuthenticator to see user token based authentication.
*
* @Route("/", name="app_login")
* @param Request $request
* @param AuthenticationUtils $authenticationUtils
* @param PageDesignRepository $pageDesignRepository
* @param CommonFunctions $commonFunctions
* @return Response
*/
public function login(Request $request, AuthenticationUtils $authenticationUtils, PageDesignRepository $pageDesignRepository, CommonFunctions $commonFunctions): Response
{
// Redirect if user already authenticated
if ($this->isGranted('ROLE_ADMIN') || $this->isGranted('ROLE_USER')) {
return $this->redirectToRoute('dashboard_index');
} else if ($this->isGranted('ROLE_BACKEND_USER')) {
return $this->redirectToRoute('admin_dashboard_index');
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
// dump($commons::$localeList);
// die;
return $this->render('security/login.html.twig',
[
'last_username' => $lastUsername,
'error' => $error,
'page_design' => $commonFunctions->findFirstByPageDesignType($pageDesignRepository, PageDesign::LOGIN_TYPE, $request->getLocale())
]
);
}
/**
* Reset Password Page
* Send password reset mail to registered users.
*
* @Route("/verify/password/send/mail", name="app_password_send_mail" )
* @param Request $request
* @param UserRepository $userRepository
* @param UserService $userService
* @param MailService $mailService
* @param TranslatorInterface $translator
* @param null $user
* @return Response
*/
public function sendEmailPasswordLink(Request $request, UserRepository $userRepository, UserService $userService, MailService $mailService, TranslatorInterface $translator, $user = null): Response
{
$form = $this->createFormBuilder()->add('email', EmailType::class)->getForm();
$form->handleRequest($request);
$sent = false;
if ($form->isSubmitted() && $form->isValid()) {
$sent = true;
$user = $userRepository->findOneBy(['email' => $form->get('email')->getData()]);
if (is_object($user)) {
$resetPassword = $userService->checkAndCreatePasswordResets($user);
try {
$mailService->sendPasswordResetEmail($user, $resetPassword);
} catch (LoaderError | RuntimeError | SyntaxError $e) {
$this->addFlash("success", $translator->trans("email.sending_error"));
}
}
//no validation anymore!
//else {
// $form->get('email')->addError(new FormError($translator->trans('msg.email_not_found')));
// }
}
return $this->render('security/verify.password.mail.html.twig', [
'resetPassword' => $form->createView(),
'sent' => $sent
]);
}
/**
* Confirm Password entry page.
* Accepts unique identifier and registered email.
* User redirected to this url only after email is triggered to their respective inbox.
*
* @Route("/reset/password", name="app_reset_password", methods={"GET","POST"})
* @param Request $request
* @param LoginFormAuthenticator $loginFormAuthenticator
* @param GuardAuthenticatorHandler $guardHandler
* @param UserValidator $userValidator
* @param TranslatorInterface $translator
* @param ResetPasswordRepository $resetPasswordRepository
* @param UserRepository $userRepository
* @param UserPasswordHasherInterface $userPasswordHasher
* @param int $flag
* @return Response
* @throws NonUniqueResultException
*/
public function recoverPassword(Request $request, LoginFormAuthenticator $loginFormAuthenticator, GuardAuthenticatorHandler $guardHandler, UserValidator $userValidator, TranslatorInterface $translator, ResetPasswordRepository $resetPasswordRepository, UserRepository $userRepository, UserPasswordHasherInterface $userPasswordHasher, $flag = 0): Response
{
if ($this->getUser() !== null) {
throw new NotFoundHttpException("You are logged in!");
}
$resetPassword = $resetPasswordRepository->findByIdentifier($request->query->get('email'), $request->query->get('identifier'));
if (!$resetPassword) {
throw new NotFoundHttpException($translator->trans('msg.email_not_found'));
}
$user = $userRepository->find($resetPassword->getUser());
if (!is_object($user)) {
throw new NotFoundHttpException($translator->trans('msg.email_not_found'));
}
// create password reset form
$form = $this->createFormBuilder($user)->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'invalid_message' => 'Password must match.',
'first_options' => ['label' => $translator->trans('user.form.password')],
'second_options' => ['label' => $translator->trans('confirm_password.confirm')],
])
->setAction($this->generateUrl('app_reset_password', ['email' => $request->query->get('email'), 'identifier' => $request->query->get('identifier')]))
->setMethod('POST')
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$password = $userValidator->validatePassword($form->get('password')->getData());
if ($password == null) {
$form->get('password')->get('first')->addError(new FormError($translator->trans('msg.password_validator_error')));
$html = $this->renderView('user/recover.password.html.twig', [
'form' => $form->createView(),
'flag' => $flag,
]);
return new Response($html, 400);
}
$em = $this->getDoctrine()->getManager();
// encode password with PasswordEncoder service
$user->setPassword($userPasswordHasher->hashPassword($user, $password));
$em->persist($user);
// successfully reset password flag to display message
$role = $user->getRoles();
$user->setResetPassword(null);
$em->persist($user);
$em->flush();
$em->remove($resetPassword);
$em->flush();
if ($role[0] == 'ROLE_BACKEND_USER') {
return $this->redirectToRoute('backend_login');
} else {
//login with the user
return $guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$loginFormAuthenticator,
'main'
);
}
}
return $this->render('user/recover.password.html.twig', [
'form' => $form->createView(),
'user' => $user
]);
}
/**
* Symfony's default logout path
* @Route("/logout", name="app_logout")
* @throws Exception
*/
public function logout()
{
throw new Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
}
/**
* @Route("/backend/logout", name="app_backend_logout")
* @throws Exception
*/
public function backendLogout()
{
throw new Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
}
/**
* Redirect based on user role after logout
*
* @Route("/logout/redirect", name="redirect_after_logout")
* @return RedirectResponse
*/
public function redirectAfterLogout(): RedirectResponse
{
$roles = $this->getUser()->getRoles();
$userRole = $roles[0];
$role = "USER";
if ($userRole == 'ROLE_SUPER_ADMIN' or $userRole == 'ROLE_BACKEND_USER') {
$role = "ADMIN";
}
// $this->redirectToRoute('app_logout');
if ($role == 'ADMIN') {
return $this->redirectToRoute('backend_login');
} else {
return $this->redirectToRoute('app_login');
}
}
/**
* Register Page
* Register user as customer.
*
* @Route("/register", name="app_register")
* @param Request $request
* @param PageDesignRepository $pageDesignRepository
* @param CommonFunctions $commonFunctions
* @param MailService $mailService
* @param TranslatorInterface $translator
* @param UserPasswordHasherInterface $userPasswordHasher
* @return Response
* @throws NoResultException
* @throws NonUniqueResultException
*/
public function register(Request $request, PageDesignRepository $pageDesignRepository, UserPasswordHasherInterface $userPasswordHasher, MailService $mailService, TranslatorInterface $translator, CommonFunctions $commonFunctions): Response
{
$user = new User();
$form = $this->createForm(RegisterUserType::class, $user, ['is_user_registration' => true]);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
if ($form->isSubmitted()) {
if ($form->isValid()) {
$emailNotUsedInOldEmailField = true;
$oldEmailRecordsCount = intval($em->getRepository(User::class)->checkEmailInOldEmailsUsed($user));
if ($oldEmailRecordsCount > 0) {
$emailNotUsedInOldEmailField = false;
}
if ($emailNotUsedInOldEmailField === true) {
$user->setPassword($userPasswordHasher->hashPassword($user, $user->generateRandomPassword()));
$user->setUsername($user->getEmail());
$user->setRoles([Commons::ROLE_ADMIN]); // set User role as customer
// create new workspace for customer and credit the user.
$workspace = new Workspace();
$workspace->setOwner($user);
$key = md5(uniqid(rand(), true));
$workspace->setCustomerKey($key);
$defaultPlan = $em->getRepository(Plan::class)->findOneBy(['defaultPlan' => true]);
if (is_object($defaultPlan)) {
$workspace->setPlan($defaultPlan);
}
// create new customer detail
$customerDetail = new CustomerDetail();
$customerDetail->setCustomer($user);
$customerDetail->setCentralEmailAddress($user->getEmail());
$em->persist($workspace);
$hash = hash('sha256', uniqid(rand(), true));
$deviceKey = new DeviceKey();
$deviceKey->setDeviceKey($hash);
$deviceKey->setKeyType("sha_256");
$deviceKey->setWorkspace($workspace);
$em->persist($deviceKey);
$user->setWorkspace($workspace);
$em->persist($user);
$em->persist($workspace);
$em->persist($customerDetail);
$em->flush();
$numLength = strlen((string)$customerDetail->getId()) + 5;
$customerId = str_pad($customerDetail->getId(), $numLength, '0', STR_PAD_LEFT);
$customerDetail->setCustomerNumber($customerId);
$em->persist($customerDetail);
$em->flush();
$resetPassword = new ResetPassword();
$resetPassword->setUser($user);
$resetPassword->setLinkExpireDateTime(new DateTime());
$em->persist($resetPassword);
$em->flush();
try {
$mailService->sendRegistrationEmail($user, $resetPassword);
} catch (LoaderError | RuntimeError | SyntaxError $e) {
$this->addFlash("success", $translator->trans("email.sending_error"));
}
try {
$mailService->sendRegistrationAdminEmail($user);
} catch (LoaderError | RuntimeError | SyntaxError $e) {
$this->addFlash("success", $translator->trans("email.sending_error"));
}
return $this->redirectToRoute('app_register_success');
}
$form->get("email")->addError(new FormError($translator->trans("entity.msg.email_exists")));
}
}
return $this->render('security/register.html.twig', [
'registrationForm' => $form->createView(),
'user' => $user,
'page_design' => $commonFunctions->findFirstByPageDesignType($pageDesignRepository, PageDesign::REGISTER_TYPE, $request->getLocale())
]);
}
/**
* Register Success Page
*
* @Route("/register/success", name="app_register_success")
* @return Response
*/
public function registerSuccess(): Response
{
return $this->render('security/register_success.html.twig');
}
/**
* Backend Login Page
* Accepts symfony's built-in guard authentication provider to authenticate user
* which is configured in security.yaml under firewall.
* Throws authenticationUtils error if user does not exists.
* Check LoginFormAuthenticator to see user token based authentication.
*
* @Route("/backend", name="backend_login")
* @param AuthenticationUtils $authenticationUtils
* @return Response
*/
public function backendLogin(AuthenticationUtils $authenticationUtils): Response
{
// Redirect if user already authenticated
if ($this->isGranted('ROLE_ADMIN') || $this->isGranted('ROLE_USER')) {
return $this->redirectToRoute('dashboard_index');
} else if ($this->isGranted('ROLE_BACKEND_USER')) {
return $this->redirectToRoute('admin_dashboard_index');
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/admin-login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
/**
* @Route("/recaptcha", name="verify_recaptcha")
* @param Request $request
* @return JsonResponse
*/
public function verifyRecaptcha(Request $request): JsonResponse
{
$errors = array();
$success = false;
if ($request->get('token')) {
$token = $request->get('token');
$recaptcha = new ReCaptcha($_ENV['GOOGLE_RECAPTCHA_SECRET']);
$resp = $recaptcha
->setScoreThreshold(0.8)
->verify($token);
if ($resp->isSuccess()) {
// Verified!
$success = true;
} else {
$errors = $resp->getErrorCodes();
}
}
return new JsonResponse(["success" => $success, "errors" => $errors]);
}
/**
* @Route("/imprint", name="imprint")
* @return Response
*/
public function imprint(): Response
{
return $this->render('security/imprint.html.twig');
}
/**
* @Route("/privacy", name="privacy")
* @return Response
*/
public function privacyPolicy(): Response
{
return $this->render('security/privacy.html.twig');
}
/**
* @Route("/gtc", name="gtc")
* @return Response
*/
public function gtc(): Response
{
return $this->render('security/gtc.html.twig');
}
/**
* @Route("/refresh", name="app_refresh")
* @return JsonResponse
*/
public function refresh(): JsonResponse
{
return new JsonResponse(['success' => 1], 200, [], false);
}
/**
* @Route("/cookie/plan/{id}", name="plan_cookie")
* @param Plan|null $plan
* @return RedirectResponse
*/
public function planCookie(Plan $plan = null): RedirectResponse
{
if (!is_null($plan)) {
setcookie('plan_register', $plan->getId(), 0, '/');
}
return $this->redirectToRoute('app_register');
}
/**
*
* @Route("/recover/email", name="app_recover_email", methods={"GET"})
* @param Request $request
* @param TranslatorInterface $translator
* @param UserRepository $userRepository
* @return Response
* @throws Exception
*/
public function recoverEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
{
if ($this->getUser() !== null) {
$this->addFlash("success", $translator->trans('email_restore.error_logged_in'));
return $this->redirectToRoute('app_login');
}
if (!$request->query->has("email") || !$request->query->has("identifier")) {
$this->addFlash("success", $translator->trans('email_restore.not_found'));
return $this->redirectToRoute('app_login');
}
$user = $userRepository->findOneBy([
'oldEmail' => $request->query->get('email'),
'emailRecoveryCode' => $request->query->get('identifier')
]
);
if (!is_object($user)) {
$this->addFlash("success", $translator->trans('email_restore.not_found'));
return $this->redirectToRoute('app_login');
}
if ($user->getEmailRecoveryCodeExpireDateTime() < new DateTime('now', new DateTimeZone("UTC"))) {
$this->addFlash("success", $translator->trans('email_restore.key_expired'));
return $this->redirectToRoute('app_login');
}
$em = $this->getDoctrine()->getManager();
$user->setEmail($user->getOldEmail());
$user->setEmailRecoveryCodeExpireDateTime(null);
$user->setEmailRecoveryCode(null);
$user->setOldEmail(null);
$em->persist($user);
$em->flush();
$role = $user->getRoles();
if ($role[0] == 'ROLE_BACKEND_USER') {
return $this->redirectToRoute('backend_login');
} else {
$this->addFlash("success", $translator->trans('email_restore.success'));
return $this->redirectToRoute('app_login');
}
}
}