<?php
namespace App\Controller\Api;
use App\Entity\Institution;
use App\Entity\PdfmenuPersonalization;
use App\Entity\Profile;
use App\Entity\Setting;
use App\Entity\User;
use App\Service\RoutingService;
use App\Service\Rsa;
use App\Service\Tools;
use App\Service\UserService;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Class AppController
* @package App\Controller\Api
* @Route(path="/api/security", name="api_security_")
*/
class SecurityController extends AbstractController
{
private EntityManagerInterface $em;
private TranslatorInterface $translator;
private MailerInterface $mailer;
private UserPasswordEncoderInterface $passwordEncoder;
private UserService $userService;
private Tools $tools;
private RoutingService $routingService;
private Rsa $rsa;
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
MailerInterface $mailer,
UserPasswordEncoderInterface $passwordEncoder,
UserService $userService,
Tools $tools,
RoutingService $routingService,
Rsa $rsa
) {
$this->em = $em;
$this->translator = $translator;
$this->mailer = $mailer;
$this->passwordEncoder = $passwordEncoder;
$this->userService = $userService;
$this->tools = $tools;
$this->routingService = $routingService;
$this->rsa = $rsa;
}
/**
* @return Response
* @Route("/login", name="login")
*/
public function login(): Response
{
return new JsonResponse([
'success' => false,
]);
}
/**
* @throws Exception
* @Route("/logout", name="logout")
*/
public function logout(): void
{
throw new Exception('This should never be reached!');
}
/**
* @param Request $request
* @return JsonResponse
* @Route("/user", name="user")
* @IsGranted("IS_AUTHENTICATED_FULLY")
*/
public function user(Request $request): JsonResponse
{
if (!$this->rsa->isValidToken($request->request->get('apiKey'))) {
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.invalidToken'),
]);
}
/** @var User $user */
$user = $this->getUser();
if (!is_null($user)) {
return new JsonResponse([
'success' => true,
]);
}
return new JsonResponse([
'success' => false,
]);
}
/**
* @param ClientRegistry $clientRegistry
* @return RedirectResponse
* @Route("/google/connect", name="google_connect")
*/
public function googleConnect(ClientRegistry $clientRegistry): RedirectResponse
{
return $clientRegistry->getClient('google')->redirect();
}
/**
* @return RedirectResponse
* @Route("/google/check", name="google_check")
*/
public function googleCheck(): RedirectResponse
{
return $this->redirectToRoute('api_security_login');
}
/**
* @param ClientRegistry $clientRegistry
* @return RedirectResponse
* @Route("/facebook/connect", name="facebook_connect")
*/
public function facebookConnect(ClientRegistry $clientRegistry): RedirectResponse
{
return $clientRegistry->getClient('facebook')->redirect();
}
/**
* @return RedirectResponse
* @Route("/facebook/check", name="facebook_check")
*/
public function facebookCheck(): RedirectResponse
{
return $this->redirectToRoute('api_security_login');
}
/**
* @param Request $request
* @return JsonResponse
* @throws \Exception
* @throws TransportExceptionInterface
* @Route("/password/reset", name="password_reset")
*/
public function passwordReset(Request $request): JsonResponse
{
if (!$this->rsa->isValidToken($request->request->get('apiKey'))) {
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.invalidToken'),
]);
}
$data = Tools::getRequestData($request);
$recaptchaToken = $data['recaptchaToken'];
$response = json_decode(file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=" . $this->getParameter('RECAPTCHA_PRIVATE_KEY') . "&response=" . $recaptchaToken . "&remoteip=" . $_SERVER['REMOTE_ADDR']), true);
if ($response['success']) {
$email = $data['email'];
/** @var User $user */
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
if ($user) {
$user->setPasswordToken($this->tools->generateRandomString(50));
$user->setPasswordRequestAt(new DateTime());
$this->em->flush();
$message = (new TemplatedEmail())
->from(new Address($this->getParameter('MAILER_FROM'), $this->getParameter('MAILER_FROM_NAME')))
->to($user->getEmail())
->subject($this->translator->trans('passwordReset.title', [], 'email'))
->htmlTemplate('front/emails/password_reset.html.twig')
->context([
'urlReset' => $this->routingService->generate('password_reset_token', ['token' => $user->getPasswordToken()], true)
]);
$this->mailer->send($message);
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans('security.passwordReset', ['%email%' => $user->getEmail()], 'messages'),
]);
}
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans('security.passwordReset', ['%email%' => $email], 'messages'),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.notRobot'),
]);
}
/**
* @param Request $request
* @return JsonResponse|RedirectResponse|Response
* @throws \Exception
* @Route("/password/reset/token", name="password_reset_token")
*/
public function passwordResetToken(Request $request): RedirectResponse|JsonResponse|Response
{
if (!$this->rsa->isValidToken($request->request->get('apiKey'))) {
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.invalidToken'),
]);
}
$data = Tools::getRequestData($request);
$token = $data['token'];
/** @var User $user */
$user = $this->em->getRepository(User::class)->findOneBy(['passwordToken' => $token]);
if (!is_null($user)) {
$now = new DateTime();
$interval = $now->diff($user->getPasswordRequestAt());
$totalMinutes = ($interval->d * 24 * 60) + ($interval->h * 60) + $interval->i;
if ($totalMinutes < 60) {
$plainPassword = null;
if (array_key_exists('password', $data)) {
$plainPassword = $data['password'];
}
$passwordConfirm = null;
if (array_key_exists('passwordConfirm', $data)) {
$passwordConfirm = $data['passwordConfirm'];
}
if ($plainPassword and $passwordConfirm) {
$recaptchaToken = $data['recaptchaToken'];
$response = json_decode(file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=" . $this->getParameter('RECAPTCHA_PRIVATE_KEY') . "&response=" . $recaptchaToken . "&remoteip=" . $_SERVER['REMOTE_ADDR']), true);
if ($response['success']) {
if ($plainPassword === $passwordConfirm) {
$checkPasswordStrengths = $this->userService->checkPasswordStrength($plainPassword);
if (count($checkPasswordStrengths) == 0) {
$password = $this->passwordEncoder->encodePassword($user, $plainPassword);
$user->setPassword($password);
$user->setPasswordToken(null);
$user->setPasswordRequestAt(null);
$this->em->flush();
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans('security.passwordResetToken.success', ['%url%' => $this->routingService->generate('login')]),
]);
}
return new JsonResponse([
'success' => false,
'message' => $checkPasswordStrengths,
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('authenticator.passwordMismatch'),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.notRobot'),
]);
}
return new JsonResponse([
'success' => true,
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('security.passwordResetToken.tokenExpired', ['%url%' => $this->routingService->generate('password_reset')]),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('security.passwordResetToken.tokenNotFound'),
]);
}
/**
* @param Request $request
* @return JsonResponse
* @throws \Exception
* @throws TransportExceptionInterface
* @Route("/registration", name="registration")
*/
public function registration(Request $request): JsonResponse
{
if (!$this->rsa->isValidToken($request->request->get('apiKey'))) {
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.invalidToken'),
]);
}
$data = Tools::getRequestData($request);
$email = $data['email'];
$plainPassword = $data['password'];
$passwordConfirm = $data['passwordConfirm'];
/** @var User $user */
$user = $this->em->getRepository(User::class)->findOneByEmail($email);
if (is_null($user)) {
if ($plainPassword and $passwordConfirm) {
if ($plainPassword === $passwordConfirm) {
$checkPasswordStrengths = $this->userService->checkPasswordStrength($plainPassword);
if (count($checkPasswordStrengths) == 0) {
$user = new User();
$user->setEmail($email);
/** @var Profile $profile */
$profile = $this->em->getRepository(Profile::class)->findOneByRole(Profile::ROLE_CUSTOMER);
/** @var Setting $setting */
$setting = $this->em->getRepository(Setting::class)->findOneBy(['name' => 'FREE_SMS_PER_MONTH']);
$user->setProfile($profile);
$password = $this->passwordEncoder->encodePassword($user, $plainPassword);
$user->setPassword($password);
$user->setHash($this->tools->generateRandomString(20));
$user->setRegistrationValidationToken($this->tools->generateRandomString(50));
$user->setRegistrationValidationRequestAt(new DateTime());
$user->setSmsRemaining($setting->getValue());
$institution = new Institution();
$institution->setIsDefault(true);
$institution->setName('Mon restaurant');
$institution->setUser($user);
$institution->setHash($this->tools->generateRandomString(20));
$pdfmenuPersonalization = new PdfmenuPersonalization();
$institution->setPdfmenuPersonalization($pdfmenuPersonalization);
$this->userService->setDefaultDataOnRegistration($institution);
$user->addInstitution($institution);
$this->em->persist($user);
$this->em->persist($pdfmenuPersonalization);
$this->em->flush();
$this->userService->generateQRCodeInstitution($institution);
$message = (new TemplatedEmail())
->from(new Address($this->getParameter('MAILER_FROM'), $this->getParameter('MAILER_FROM_NAME')))
->to($user->getEmail())
->subject($this->translator->trans('registration.title', [], 'email'))
->htmlTemplate('front/emails/registration.html.twig')
->context([
'userEmail' => $user->getEmail(),
'urlValidation' => $this->routingService->generate('registration_validation', ['token' => $user->getRegistrationValidationToken()], true),
]);
$this->mailer->send($message);
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans('registration.success', ['%url%' => $this->routingService->generate('login')]),
]);
}
return new JsonResponse([
'success' => false,
'message' => $checkPasswordStrengths,
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('authenticator.passwordMismatch'),
]);
}
return new JsonResponse([
'success' => false,
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('registration.userExist', ['%url%' => $this->routingService->generate('password_reset')]),
]);
}
/**
* @param Request $request
* @return JsonResponse
* @throws \Exception
* @Route("/registration/validation", name="registration_validation")
*/
public function registrationValidation(Request $request): JsonResponse
{
if (!$this->rsa->isValidToken($request->request->get('apiKey'))) {
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.invalidToken'),
]);
}
$data = Tools::getRequestData($request);
$token = $data['token'];
/** @var User $user */
$user = $this->em->getRepository(User::class)->findOneBy(['registrationValidationToken' => $token]);
if (!is_null($user)) {
$now = new DateTime();
$interval = $now->diff($user->getRegistrationValidationRequestAt());
$totalMinutes = ($interval->d * 24 * 60) + ($interval->h * 60) + $interval->i;
if ($totalMinutes < 120) {
$user->setValidated(true);
$user->setRegistrationValidationToken(null);
$user->setRegistrationValidationRequestAt(null);
$this->em->persist($user);
$this->em->flush();
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans('registration.validation.success', ['%url%' => $this->routingService->generate('login')]),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('registration.validation.tokenExpired', ['%url%' => $this->routingService->generate('registration_validation_email', ['token' => $token])]),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('registration.validation.tokenNotFound'),
]);
}
/**
* @param Request $request
* @return JsonResponse
* @throws \Exception
* @throws TransportExceptionInterface
* @Route("/registration/validation/email", name="registration_validation_email")
*/
public function registrationValidationEmail(Request $request): JsonResponse
{
if (!$this->rsa->isValidToken($request->request->get('apiKey'))) {
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('global.invalidToken'),
]);
}
$data = Tools::getRequestData($request);
$token = $data['token'];
/** @var User $user */
$user = $this->em->getRepository(User::class)->findOneBy(['registrationValidationToken' => $token]);
if (!is_null($user)) {
$now = new DateTime();
$interval = $now->diff($user->getRegistrationValidationRequestAt());
$totalMinutes = intval(($interval->d * 24 * 60) + ($interval->h * 60) + $interval->i);
if ($totalMinutes > 60) {
$user->setRegistrationValidationToken($this->tools->generateRandomString(50));
$user->setRegistrationValidationRequestAt(new DateTime());
$this->em->persist($user);
$this->em->flush();
$message = (new TemplatedEmail())
->from(new Address($this->getParameter('MAILER_FROM'), $this->getParameter('MAILER_FROM_NAME')))
->to($user->getEmail())
->subject($this->translator->trans('registrationEmail.title', [], 'email'))
->htmlTemplate('front/emails/registration_email.html.twig')
->context([
'urlValidation' => $this->routingService->generate('registration_validation', ['token' => $user->getRegistrationValidationToken()], true),
]);
$this->mailer->send($message);
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans('registration.validationEmail.success'),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('registration.validationEmail.tokenAlreadySent'),
]);
}
return new JsonResponse([
'success' => false,
'message' => $this->translator->trans('registration.validationEmail.tokenNotFound'),
]);
}
}