src/Controller/Api/Auth.php line 562

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by PhpStorm.
  4.  * User: adria
  5.  * Date: 1/3/2019
  6.  * Time: 12:11 AM
  7.  */
  8. namespace App\Controller\Api;
  9. use App\Controller\Response;
  10. use App\Entity\ApiToken;
  11. use App\Entity\UserFcmTokens;
  12. use App\Entity\Users;
  13. use App\Entity\UserVerificationTokens;
  14. use App\Message\SendResetPasswordMessage;
  15. use App\Message\SendVerifyAccountMessage;
  16. use App\Helper\ConstantValues;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  19. use Symfony\Component\HttpFoundation\JsonResponse;
  20. use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\Messenger\MessageBusInterface;
  23. use Symfony\Component\Routing\Annotation\Route;
  24. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  25. use Symfony\Contracts\Translation\TranslatorInterface;
  26. use Psr\Log\LoggerInterface;
  27. use OpenApi\Attributes as OA;
  28. use Nelmio\ApiDocBundle\Annotation\Model;
  29. class Auth extends AbstractController
  30. {
  31.     use Response;
  32.     use \App\Controller\Request;
  33.     protected $translator;
  34.     protected $logger;
  35.     public function __construct(TranslatorInterface $translatorLoggerInterface $logger)
  36.     {
  37.         $this->translator $translator;
  38.         $this->logger $logger;
  39.     }
  40.     #[Route("/api/user/register"name"register_user"methods: ["POST"]),
  41.         OA\Tag(name"Access"),
  42.         OA\RequestBody(
  43.             content: new OA\JsonContent(
  44.                 properties: [
  45.                     new OA\Property(property"email"type"string"),
  46.                     new OA\Property(property"source"type"string"),
  47.                     new OA\Property(property"lang"type"string"),
  48.                     new OA\Property(property"tos_consent"type"integer"),
  49.                     new OA\Property(property"gdpr_consent"type"integer"),
  50.                     new OA\Property(property"firstname"type"string"),
  51.                     new OA\Property(property"lastname"type"string"),
  52.                     new OA\Property(property"mobile_phone"type"string"),
  53.                     new OA\Property(property"password"type"string")
  54.                 ]
  55.             )
  56.         ),
  57.         OA\Response(
  58.             response200,
  59.             description"successful register",
  60.             content: new OA\JsonContent(
  61.                 properties: [
  62.                     new OA\Property(property"user"
  63.                         ref: new Model(typeUsers::class))
  64.                 ]
  65.             )
  66.         )
  67.     ]
  68.     public function registerAction(
  69.         Request                     $request,
  70.         EntityManagerInterface      $em,
  71.         UserPasswordHasherInterface $passwordHasher,
  72.         MessageBusInterface         $bus
  73.     )
  74.     {
  75.         # get Request json
  76.         $content $request->getContent();
  77.         # parse JSON to Array
  78.         $data \json_decode($contenttrue);
  79.         $lang $this->getLangFromData($data);
  80.         if (!isset($data['email']) || !$this->isValidEmail($data['email'])) {
  81.             return $this->errorJsonResponse(['response' => $this->translator->trans('Invalid email address', [], 'messages'$lang)]);
  82.         }
  83.         # get doctrine repository : AppUsers
  84.         $repository $em->getRepository(Users::class);
  85.         # find if an user with this email exists
  86.         $user $repository->findOneBy(['emailAddress' => $data['email']]);
  87.         # add new user if the email is not used
  88.         if (!$user) {
  89.             if (empty($data['tos_consent']) || empty($data['gdpr_consent'])) {
  90.                 return $this->errorJsonResponse(
  91.                     [
  92.                         'response' => $this->translator->trans('Compliance not accepted', [], 'messages'$lang),
  93.                         'errors' => ['tos_consent''gdpr_consent']
  94.                     ],
  95.                     SymfonyResponse::HTTP_OK);
  96.             }
  97.             $user = new Users();
  98.             $user->setEmailAddress($data['email']);
  99.             $user->setUsername($data['email']);
  100.             $user->setFirstname(isset($data['firstname']) ? $data['firstname'] : "");
  101.             $user->setLastname(isset($data['lastname']) ? $data['lastname'] : "");
  102.             $user->setMobilePhone(isset($data['mobile_phone']) ? $data['mobile_phone'] : null);
  103.             $user->setFullname($user->getFirstname() . ' ' $user->getLastname());
  104.             $user->setRegisterSource(isset($data['source']) ? $data['source'] : 'app');
  105.             $user->setCreatedAt(new \DateTime());
  106.             $user->setUpdatedAt(new \DateTime());
  107.             $user->setLang($lang);
  108.             $user->setActive(true);
  109.             $user->setUnitMeasure(1);
  110.             $user->setRemoved(false);
  111.             $user->setUserPoints(0);
  112.             $user->setHealthyMinutes(0);
  113.             $user->setEmailVerified(false);
  114.             $user->setTosConsentAt(new \DateTime());
  115.             $user->setGdprConsentAt(new \DateTime());
  116.             $encoded $passwordHasher->hashPassword($user$data['password']);
  117.             $user->setPassword($encoded);
  118.             $em->persist($user);
  119.             $em->flush();
  120.             $em->refresh($user);
  121.             # send verification email
  122.             $verificationCode $em
  123.                 ->getRepository(UserVerificationTokens::class)
  124.                 ->generate($user->getId());
  125.             # preparing URLs
  126.             $verificationUrl rtrim($this->getParameter('application_web_url'), '/')
  127.                 .'/email_verification/'
  128.                 .$verificationCode->getToken();
  129.             $wasNotMeUrl $verificationUrl.'/was_not_me'.'?lang='.$lang;
  130.             $verificationUrl $verificationUrl.'?lang='.$lang;
  131.             # sending email
  132.             $bus->dispatch(
  133.                 (new SendVerifyAccountMessage)
  134.                 ->setBaseApiUrl($this->getParameter("application_api_url"))
  135.                 ->setFrom($this->getParameter('mailer_from_address'))
  136.                 ->setUser($user)
  137.                 ->setSubject($this->translator->trans('Email verification subject', [], 'messages'$lang))
  138.                 ->setView('emails/accountVerification' ucfirst($lang) . '.html.twig')
  139.                 ->setVerificationUrl($verificationUrl)
  140.                 ->setWasNotMeUrl($wasNotMeUrl)
  141.             );
  142.         } else {
  143.             return $this->errorJsonResponse(['response' => $this->translator->trans('Email used', [], 'messages'$lang)]);
  144.         }
  145.         return $this->jsonResponse(['user' => $repository->getUser($user->getId())]);
  146.     }
  147.     #[Route("/api/user/sign_in"name"sign_in"methods: ["POST"]),
  148.         OA\Tag(name"Access"),
  149.         OA\RequestBody(
  150.             content: new OA\JsonContent(
  151.                 properties: [
  152.                     new OA\Property(property"email"type"string"),
  153.                     new OA\Property(property"source"type"string"),
  154.                     new OA\Property(property"lang"type"string"),
  155.                     new OA\Property(property"tos_consent"type"integer"),
  156.                     new OA\Property(property"gdpr_consent"type"integer"),
  157.                     new OA\Property(property"firstname"type"string"),
  158.                     new OA\Property(property"lastname"type"string"),
  159.                     new OA\Property(property"mobile_phone"type"string"),
  160.                     new OA\Property(property"password"type"string")
  161.                 ]
  162.             )
  163.         ),
  164.         OA\Response(
  165.             response200,
  166.             description"successful login",
  167.             content: new OA\JsonContent(
  168.                 properties: [
  169.                     new OA\Property(
  170.                         property"api_token"
  171.                         type"object"
  172.                         properties: [
  173.                             new OA\Property(property"token"type"string"),
  174.                             new OA\Property(
  175.                                 property"expiresAt"type"datetime")
  176.                         ]
  177.                     ),
  178.                     new OA\Property(property"user"
  179.                         ref: new Model(typeUsers::class))
  180.                 ]
  181.             )
  182.         )
  183.     ]
  184.     public function signInAction(
  185.         Request $requestEntityManagerInterface $emUserPasswordHasherInterface $passwordHasher
  186.     )
  187.     {
  188.         # get Request json
  189.         $content $request->getContent();
  190.         # parse JSON to Array
  191.         $data \json_decode($contenttrue);
  192.         $lang $this->getLangFromData($data);
  193.         if (!isset($data['email']) || !$this->isValidEmail($data['email'])) {
  194.             return $this->errorJsonResponse(['response' => $this->translator->trans('Invalid email address', [], 'messages'$lang)]);
  195.         }
  196.         # get doctrine repository : AppUsers
  197.         $repository $em->getRepository(Users::class);
  198.         # find if an user with this email exists
  199.         $user $repository->findOneBy(['emailAddress' => $data['email']]);
  200.         # sign in with FACEBOOK OR GMAIL
  201.         if (isset($data['source']) && in_array($data['source'], ['facebook''gmail''apple'])) {
  202.             if (!$user) {
  203.                 if (empty($data['tos_consent']) || empty($data['gdpr_consent'])) {
  204.                     return $this->errorJsonResponse(
  205.                         [
  206.                             'response' => $this->translator->trans('Compliance not accepted', [], 'messages'$lang),
  207.                             'errors' => ['tos_consent''gdpr_consent']
  208.                         ],
  209.                         SymfonyResponse::HTTP_NOT_FOUND);
  210.                 }
  211.                 $user = new Users();
  212.                 $user->setEmailAddress($data['email']);
  213.                 $user->setUsername($data['email']);
  214.                 $user->setFirstname(isset($data['firstname']) ? $data['firstname'] : null);
  215.                 $user->setLastname(isset($data['lastname']) ? $data['lastname'] : null);
  216.                 $user->setMobilePhone(isset($data['mobile_phone']) ? $data['mobile_phone'] : null);
  217.                 $user->setFullname($user->getFirstname() . ' ' $user->getLastname());
  218.                 $user->setRegisterSource(isset($data['source']) ? $data['source'] : 'app');
  219.                 $user->setCreatedAt(new \DateTime());
  220.                 $user->setUpdatedAt(new \DateTime());
  221.                 $user->setLang($lang);
  222.                 $user->setActive(true);
  223.                 $user->setRemoved(false);
  224.                 $user->setRegisterSource($data['source']);
  225.                 $user->setUnitMeasure(1);
  226.                 $user->setUserPoints(0);
  227.                 $user->setHealthyMinutes(0);
  228.                 $user->setEmailVerified(true);
  229.                 $user->setTosConsentAt(new \DateTime());
  230.                 $user->setGdprConsentAt(new \DateTime());
  231.                 $encoded $passwordHasher->hashPassword($user, (isset($data['password']) ? $data['password'] : 1234));
  232.                 $user->setPassword($encoded);
  233.                 $em->persist($user);
  234.                 $em->flush();
  235.             }
  236.         } else {
  237.             if (!$user || !$passwordHasher->isPasswordValid($user$data['password'])) {
  238.                 return $this->errorJsonResponse(['response' => $this->translator->trans('Invalid credentials', [], 'messages'$lang)]);
  239.             }
  240.         }
  241.         $user->setLang($lang);
  242.         // not allow log-in of unverified addresses
  243.         if (!$user->getEmailVerified()) {
  244.             return $this->explicitErrorJsonResponse(
  245.                 ['response' => $this->translator->trans('Email verification error', [], 'messages'$lang)],
  246.                 SymfonyResponse::HTTP_OK,
  247.                 errors: [ConstantValues::ISSUE_EMAIL_NOT_VERIFIED],
  248.             );
  249.         }
  250.         // generate apiToken
  251.         $apiToken = new ApiToken(
  252.             $user,
  253.             new \DateTime(sprintf('+%d seconds'$this->getParameter('auth_token_expiration_seconds')))
  254.         );
  255.         $em->persist($apiToken);
  256.         $em->persist($user);
  257.         $em->flush();
  258.         # send status ok
  259.         return $this->jsonResponse([
  260.             'user' => $repository->getUser($user->getId()),
  261.             'api_token' => [
  262.                 'token' => $apiToken->getToken(),
  263.                 'expiresAt' => $apiToken->getExpiresAt(),
  264.             ],
  265.         ]);
  266.     }
  267.     /**
  268.      * @Route("/api/user/{id}/token", name="save_user_token", methods={"POST"})
  269.      */
  270.     #[Route("/api/user/{id}/token"
  271.             name"save_user_token"methods: ["POST"]),
  272.         OA\Tag(name"Access"),
  273.         OA\RequestBody(
  274.             content: new OA\JsonContent(
  275.                 properties: [
  276.                     new OA\Property(property"token"type"string"
  277.                         description"user token"),
  278.                 ]
  279.             )
  280.         )
  281.     ]
  282.     public function saveUserTokenAction($idRequest $requestEntityManagerInterface $em)
  283.     {
  284.         try {
  285.             $user $this->getUser(); // TOKEN
  286.             if (!$user or ! $user instanceof Users) {
  287.                 $type is_null($user) ? 'null' get_class($user);
  288.                 return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
  289.             }
  290.             # get Request json
  291.             $content $request->getContent();
  292.             # parse JSON to Array
  293.             $data \json_decode($contenttrue);
  294.             $lang $this->getLangFromData($data);
  295.             if (!isset($data['token']) || empty($data['token'])) {
  296.                 return $this->errorJsonResponse(['response' => $this->translator->trans('Token not found', [], 'messages'$lang)]);
  297.             }
  298.             # get doctrine repository : AppUsers
  299.             $repository $em->getRepository(UserFcmTokens::class);
  300.             # find if an user with this email exists
  301.             $token $repository->findOneBy([
  302.                 'token' => $data['token'],
  303.             ]);
  304.             $token_exists false;
  305.             if (!$token) {
  306.                 $token = new UserFcmTokens();
  307.                 $token->setUserId($user->getId());
  308.                 $token->setRemoved(false);
  309.                 $token->setToken($data['token']);
  310.             } else {
  311.                 $token->setUserId($user->getId());
  312.                 $token_exists true;
  313.             }
  314.             $em->persist($token);
  315.             $em->flush();
  316.         } catch (\Exception $exception) {
  317.             $this->logger->error(json_encode([
  318.                 'timestamp' => (new \DateTime())->format(\DateTime::ATOM),
  319.                 'endpoint' => sprintf('api/user/%d/token'$id),
  320.                 'error' => $exception->getMessage(),
  321.                 'data' => $data,
  322.                 'token_exists' => $token_exists
  323.         ]));
  324.     }
  325.         # send status ok regardless
  326.         return $this->jsonResponse([]);
  327.     }
  328.     #[Route("/api/user/{id}/update"name"update_user"methods: ["PUT"]),
  329.         OA\Tag(name"Access"),
  330.         OA\RequestBody(
  331.             content: new OA\JsonContent(
  332.                 properties: [
  333.                     new OA\Property(property"email"type"string"),
  334.                     new OA\Property(property"lang"type"string"),
  335.                     new OA\Property(property"tos_consent"type"integer"),
  336.                     new OA\Property(property"gdpr_consent"type"integer"),
  337.                     new OA\Property(property"firstname"type"string"),
  338.                     new OA\Property(property"lastname"type"string"),
  339.                     new OA\Property(property"mobile_phone"type"string"),
  340.                     new OA\Property(property"password"type"string"),
  341.                     new OA\Property(property"unit_measure"type"integer")
  342.                 ]
  343.             )
  344.         ),
  345.         OA\Response(
  346.             response200,
  347.             description"successful update",
  348.             content: new OA\JsonContent(
  349.                 properties: [
  350.                     new OA\Property(property"user"
  351.                         ref: new Model(typeUsers::class))
  352.                 ]
  353.             )
  354.         )
  355.     ]
  356.     public function updateUserAction(
  357.         $idRequest $requestEntityManagerInterface $emUserPasswordHasherInterface $passwordHasher
  358.     )
  359.     {
  360.         try {
  361.             $user $this->getUser(); // TOKEN
  362.             if (!$user or ! $user instanceof Users) {
  363.                 $type is_null($user) ? 'null' get_class($user);
  364.                 return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
  365.             }
  366.             # get Request json
  367.             $content $request->getContent();
  368.             # parse JSON to Array
  369.             $data \json_decode($contenttrue);
  370.             $lang $this->getLangFromData($data);
  371.             if (!isset($data['email']) || !$this->isValidEmail($data['email'])) {
  372.                 return $this->errorJsonResponse(['response' => $this->translator->trans('Invalid email address', [], 'messages'$lang)]);
  373.             }
  374.             # get doctrine repository : AppUsers
  375.             $repository $em->getRepository(Users::class);
  376.             if ($user) {
  377.                 if ($user->getEmailAddress() !== $data['email']) {
  378.                     # get doctrine repository : AppUsers
  379.                     $checkUser $em->createQueryBuilder()
  380.                         ->select('u')
  381.                         ->from(Users::class, 'u')
  382.                         ->where('u.id != :id AND u.emailAddress = :email')
  383.                         ->setParameter('id'$id)
  384.                         ->setParameter('email'$data['email'])
  385.                         ->getQuery()
  386.                         ->getResult();
  387.                     if ($checkUser) {
  388.                         return $this->errorJsonResponse(['response' => $this->translator->trans('Email used', [], 'messages'$lang)]);
  389.                     }
  390.                 }
  391.                 $user->setEmailAddress($data['email']);
  392.                 $user->setUsername($data['email']);
  393.                 $user->setFirstname(isset($data['firstname']) ? $data['firstname'] : $user->getFirstname());
  394.                 $user->setLastname(isset($data['lastname']) ? $data['lastname'] : $user->getLastname());
  395.                 $user->setMobilePhone(isset($data['mobile_phone']) ? $data['mobile_phone'] : $user->getMobilePhone());
  396.                 $user->setFullname($user->getFirstname() . ' ' $user->getLastname());
  397.                 $user->setLang($lang);
  398.                 $user->setUnitMeasure($data['unit_measure'] ?? 1);
  399.                 if (!empty($data['tos_consent'])) {
  400.                     $user->setTosConsentAt(new \DateTime());
  401.                 }
  402.     
  403.                 if (!empty($data['gdpr_consent'])) {
  404.                     $user->setGdprConsentAt(new \DateTime());
  405.                 }
  406.                 if (!empty($data['password'])) {
  407.                     $encoded $passwordHasher->hashPassword($user$data['password']);
  408.                     $user->setPassword($encoded);
  409.                 }
  410.                 $em->persist($user);
  411.                 $em->flush();
  412.                 # send status ok
  413.                 return $this->jsonResponse([
  414.                     'status' => 'OK',
  415.                     'user' => $repository->getUser($user->getId()),
  416.                 ]);
  417.             } else {
  418.                 return $this->errorJsonResponse(['response' => $this->translator->trans('User not found', [], 'messages'$lang)]);
  419.             }
  420.         } catch (\Exception $exception) {
  421.             return new JsonResponse([
  422.                 'status' => 'error',
  423.                 'message' => $exception->getMessage(),
  424.             ], 200);
  425.         }
  426.     }
  427.     #[Route("/api/user/reset_password"
  428.             name"reset_user_password"methods: ["POST"]),
  429.         OA\Tag(name"Access"),
  430.         OA\RequestBody(
  431.             content: new OA\JsonContent(
  432.                 properties: [
  433.                     new OA\Property(property"email_address"type"string"
  434.                         description"user email address"),
  435.                     new OA\Property(property"lang"type"string"
  436.                         description"language code")
  437.                 ]
  438.             )
  439.         )
  440.     ]
  441.     public function resetPasswordAction(
  442.         Request                     $request,
  443.         UserPasswordHasherInterface $passwordHasher,
  444.         EntityManagerInterface      $em,
  445.         MessageBusInterface         $bus
  446.     )
  447.     {
  448.         try {
  449.             # get Request json
  450.             $content $request->getContent();
  451.             # parse JSON to Array
  452.             $data \json_decode($contenttrue);
  453.             $lang $this->getLangFromData($data);
  454.             if (!isset($data['email']) || !$this->isValidEmail($data['email'])) {
  455.                 return $this->errorJsonResponse(['response' => $this->translator->trans('Invalid email address', [], 'messages'$lang)]);
  456.             }
  457.             # get doctrine repository : AppUsers
  458.             $repository $em->getRepository(Users::class);
  459.             # find if an user with this email exists
  460.             $user $repository->findOneBy([
  461.                 'emailAddress' => $data['email'],
  462.             ]);
  463.             if ($user) {
  464.                 $newPassword \bin2hex(\random_bytes(5));
  465.                 $encoded $passwordHasher->hashPassword($user$newPassword);
  466.                 $user->setPassword($encoded);
  467.                 $em->persist($user);
  468.                 $em->flush();
  469.                 $bus->dispatch(
  470.                     (new SendResetPasswordMessage)
  471.                     ->setBaseApiUrl($this->getParameter("application_api_url"))
  472.                     ->setFrom($this->getParameter('mailer_from_address'))
  473.                     ->setUser($user)
  474.                     ->setSubject($this->translator->trans('Reset password', [], 'messages'$lang))
  475.                     ->setView('emails/resetPassword' ucfirst($lang) . '.html.twig')
  476.                     ->setPassword($newPassword)
  477.                 );
  478.             } else {
  479.                 return $this->errorJsonResponse(['response' => $this->translator->trans('User not found', [], 'messages'$lang)]);
  480.             }
  481.             # send status ok
  482.             return $this->jsonResponse([]);
  483.         } catch (\Exception $exception) {
  484.             return new JsonResponse([
  485.                 'status' => 'error',
  486.                 'message' => $exception->getMessage(),
  487.             ], 200);
  488.         }
  489.     }
  490.     #[Route("/api/user/{id}"name"get_user_details"methods: ["GET"]),
  491.         OA\Tag(name"Access"),
  492.         OA\Response(
  493.             response200,
  494.             description"successful fetch",
  495.             content: new OA\JsonContent(
  496.                 properties: [
  497.                     new OA\Property(property"user"
  498.                         ref: new Model(typeUsers::class))
  499.                 ]
  500.             )
  501.         )
  502.     ]
  503.     public function getUserAction($idEntityManagerInterface $em)
  504.     {
  505.         $user $this->getUser(); // TOKEN
  506.         if (!$user or ! $user instanceof Users) {
  507.             $type is_null($user) ? 'null' get_class($user);
  508.             return $this->errorJsonResponse(['message' => "User not found or invalid ($type)"]);
  509.         }
  510.         # get doctrine repository : AppUsers
  511.         $repository $em->getRepository(Users::class);
  512.         # send status ok
  513.         return $this->jsonResponse(['user' => $repository->getUser($user->getId())]);
  514.     }
  515. }