<?php
namespace MyBundle\Controller;
use CoreBundle\Entity\User;
use CoreBundle\Repository\UserRepository;
use DateTime;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
use MyBundle\Component\InfobipSmsSender;
use MyBundle\Entity\AttemptLogin;
use MyBundle\Entity\CrossDomainAuthToken;
use MyBundle\Entity\Otp;
use MyBundle\Entity\RegisterRequest;
use MyBundle\Entity\UserReferral;
use MyBundle\Enum\AuthDataKeys;
use MyBundle\Enum\RegistrationStatusEnums;
use MyBundle\Exception\DataNotValidException;
use MyBundle\Exception\UserNotFoundException;
use MyBundle\Factory\AuthCodeDTOFactory;
use MyBundle\Factory\OtpCodeGenerator;
use MyBundle\Factory\OtpEntityFactory;
use MyBundle\Factory\RegUserDTOFactory;
use MyBundle\Factory\StringGenerator;
use MyBundle\Factory\UserEntityFactory;
use MyBundle\Messages;
use MyBundle\Service\AuthDataValidator;
use MyBundle\Service\AuthService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
class AuthController extends MyBaseController
{
use TargetPathTrait;
private SessionInterface $session;
private TokenStorageInterface $tokenStorage;
public function __construct(EntityManagerInterface $em, RequestStack $requestStack, Messages $messages,
\CoreBundle\Model\User $userModel, SessionInterface $session,
TokenStorageInterface $tokenStorage, Environment $twig)
{
parent::__construct($em, $requestStack, $messages, $userModel, $twig);
$this->session = $session;
$this->tokenStorage = $tokenStorage;
}
public function logout(AuthService $authService): RedirectResponse
{
if ($this->getUser()) {
$authService->logoutUser($this->getUser());
}
return $this->redirectToRoute('my_login_page');
}
public function resendSms(Request $request): JsonResponse
{
$hash = $request->get('hash');
if (!$this->userModel->initByHash($hash)) {
return $this->errorResponce(['total' => 'Виникла помилка, будь ласка, спробуйте ще раз пізніше']);
}
if ($this->userModel->sendConfirmSms()) {
return new JsonResponse(['success' => true, 'phone' => $this->userModel->getPhone()]);
}
return $this->errorResponce(['total' => 'Ви використали всі спроби доставки sms']);
}
public function confirmPhone(Request $request)
{
$hash = $request->request->get('hash');
if (!$this->userModel->initByHash($hash)) {
return new JsonResponse(['success' => false]);
}
$code = $request->request->get('code');
if (!$this->userModel->checkConfirmPhone($code)) {
return new JsonResponse(['success' => false]);
}
$this->userModel->senConfirmEmail();
return new JsonResponse(['success' => true, 'hash' => $hash]);
}
public function reSendEmail(Request $request): JsonResponse
{
$hash = $request->get('hash');
if (!$this->userModel->initByHash($hash)) {
return new JsonResponse(['success' => false]);
}
$this->userModel->senConfirmEmail();
return new JsonResponse(['success' => true]);
}
public function confirmEmail(Request $request): ?Response
{
if (!$this->userModel->initByHash($request->get('hash'))) {
throw new NotFoundHttpException();
}
return $this->baseMyRender('@My/Auth/confirm_email.html.twig', [
'user' => $this->userModel->getUser()
]);
}
public function finish($userHash, $hash): ?Response
{
$success = false;
if ($this->userModel->initByHash($userHash)) {
$success = $this->userModel->confirmEmail($hash);
}
return $this->baseMyRender(
'@My/Auth/finish_register.html.twig',
['success' => $success, 'ehash' => $this->userModel->getUser()->getEmailHash(), 'hash' => $this->userModel->getHash()]
);
}
public function setPassword(Request $request, EventDispatcherInterface $eventDispatcher): JsonResponse
{
$hash = $request->get('userHash');
if (!$this->userModel->initByHash($hash)) {
return new JsonResponse(['success' => false]);
}
$emailHash = $request->request->get('ehash');
if ($emailHash != $this->userModel->getUser()->getEmailHash()) {
return new JsonResponse(['success' => false]);
}
$password = $request->request->get('password');
$this->userModel->setPassword($password);
$token = new UsernamePasswordToken($this->userModel->getUser(), null, 'prifile', $this->userModel->getUser()->getRoles());
$this->tokenStorage->setToken($token);
$this->session->set('_security_prifile', serialize($token));
$event = new InteractiveLoginEvent($request, $token);
//TODO refactor this to correct dispatcher
$eventDispatcher->dispatch($event, "security.interactive_login");
return new JsonResponse(['success' => true]);
}
public function recover($hash, $ehash)
{
if (!$this->userModel->initByHash($hash)) {
throw $this->createAccessDeniedException();
}
if ($ehash != $this->userModel->getUser()->getEmailHash()) {
throw $this->createAccessDeniedException();
}
return $this->baseMyRender('@My/Auth/recover_pass.html.twig', [
'hash' => $hash,
'ehash' => $ehash,
]);
}
public function loginCheck(Request $request, AuthService $authService, EncoderFactoryInterface $factory): JsonResponse
{
$phone = $request->request->get('phone');
$phone = substr(preg_replace('~[^0-9]+~', '', $phone), -10);
$_password = $request->get('password');
$cardNumber = $request->request->get('card');
$cardNumber = preg_replace('~[^0-9]+~', '', $cardNumber);
$login = $request->get('login');
if ($login) {
$user = $this->em->getRepository(User::class)->findOneBy(['username' => $login]);
} else {
$user = $this->em->getRepository(User::class)->findByPhoneOrCardNum($phone, $cardNumber);
}
if (!$user) {
return new JsonResponse(['success' => false, 'error' => 'account is not found']);
}
$encoder = $factory->getEncoder($user);
$salt = $user->getSalt();
if (!$encoder->isPasswordValid($user->getPassword(), $_password, $salt)) {
return new JsonResponse(['success' => false, 'error' => 'password wrong']);
}
$this->userModel->pushReferrer($request, $user, User::UTM_LOGIN);
return $authService->loginUser($user);
}
public function login(Request $request, EventDispatcherInterface $eventDispatcher, RouterInterface $router, AuthService $authService): RedirectResponse|Response|null
{
if ($this->getUser()) {
$referer = $authService->getRefererUser($request, $this->getUser());
if ($referer) {
return $this->redirect($referer);
}
}
$url = $this->getTargetPath($request->getSession(), 'main');
if ($this->getUser() && !$referer) {
return $this->redirect($router->generate('my_about_car_page'));
}
$tokenStr = $request->query->get('token');
if ($tokenStr) {
$AuthToken =
$this->em
->getRepository(CrossDomainAuthToken::class)
->findOneBy(['token' => $tokenStr]);
if (!$AuthToken) {
return $this->baseMyRender('@My/Auth/login.html.twig');
}
$now = new DateTime();
$tokenCreateDate = $AuthToken->getDateCreate();
//TODO remove to some config
if ($tokenCreateDate < $now->modify('-30 min')) {
return $this->baseMyRender('@My/Auth/login.html.twig');
}
$user = $AuthToken->getUser();
$token = new UsernamePasswordToken($user, null, 'profile', $user->getRoles());
$this->tokenStorage->setToken($token);
$this->session->set('_security_profile', serialize($token));
$event = new InteractiveLoginEvent($request, $token);
//TODO refactor this to correct dispatcher
$eventDispatcher->dispatch($event, "security.interactive_login");
$this->em->remove($AuthToken);
$this->em->flush();
return $this->redirect($request->headers->get('referer'));
}
return $this->baseMyRender('@My/Auth/login.html.twig', [
'backLoginUrl' => $url
]);
}
public function register(
Request $request,
AuthDataValidator $validator,
LoggerInterface $logger,
UserEntityFactory $userEntityFactory,
RegUserDTOFactory $userDTOFactory,
OtpCodeGenerator $codeGenerator,
TranslatorInterface $translator
): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$data = $request->request->all();
try {
$validator->validateForRegisterUser($data);
} catch (DataNotValidException $e) {
$logger->error($e->getMessage(), $data);
return new JsonResponse([
'success' => false,
'message' => $translator->trans('auth.error', [], 'loyality'),
]);
}
$userRegDTO = $userDTOFactory->createDefaultAuthDTO($data);
/** @var UserRepository $users */
$users = $this->em->getRepository(User::class);
$userCount = $users->checkRegisteredUsersByParams(
[
'phone' => $userRegDTO->phone,
'email' => $userRegDTO->email
]
);
if ($userCount) {
return new JsonResponse([
'success' => false,
'message' => $translator->trans('auth.duplicate_email_or_phone', [], 'loyality'),
]);
}
$registerRequest = new RegisterRequest();
$registerRequest->setDateCreate(new DateTime());
$registerRequest->setPhone($userRegDTO->phone);
$registerRequest->setRequest(json_encode($data));
$registerRequest->setIsComplete(false);
$this->em->persist($registerRequest);
$this->em->flush();
$otp = $codeGenerator->generate();
$registerRequest->setOtp($otp);
$registerRequest->setOtpExpDate((new DateTime())->modify('+1 hour'));
try {
$em = $this->em;
$em->persist($registerRequest);
$em->flush();
} catch (UniqueConstraintViolationException $ex) {
return new JsonResponse([
'success' => false,
'message' => $translator->trans('auth.duplicate_email', [], 'loyality'),
]);
}
return new JsonResponse([
'success' => true,
'requestId' => $registerRequest->getId(),
'message' => $translator->trans(
'auth.sending_to_phone_succeed',
['%phone%' => $this->getSecurePhone($registerRequest->getPhone())],
'loyality'
),
]);
}
public function getCode(
Request $request,
AuthDataValidator $validator,
LoggerInterface $logger,
AuthCodeDTOFactory $DTOFactory,
OtpEntityFactory $otpEntityFactory,
UserEntityFactory $userEntityFactory,
RegUserDTOFactory $userDTOFactory,
TranslatorInterface $translator,
InfobipSmsSender $infobipSmsSender,
AuthService $authService
): JsonResponse
{
$data = $request->request->all();
$phone = substr(preg_replace('~[^0-9]+~', '', $data[AuthDataKeys::EMAIL]), -10);
if ($request->request->get('loginPhone') == 'false') {
$email = $request->request->get('email');
$userAuth = $DTOFactory->getUserByEmail($email);
} else {
$userAuth = $DTOFactory->getUserByPhone($phone);
}
$requestId = $request->get('requestId');
if (!$userAuth && $requestId) {
$RegisterRequest = $this->em->getRepository(RegisterRequest::class)->find($requestId);
if (!$RegisterRequest) {
return new JsonResponse(['success' => false, 'error' => $translator->trans('auth.error', [], 'loyality')]);
}
$requestData = json_decode($RegisterRequest->getRequest());
$requestEmail = $requestData->email;
if (($phone != '' && $RegisterRequest->getPhone() != $phone) || ($email && $requestEmail != $email)) {
return new JsonResponse(['success' => false, 'error' => $translator->trans('auth.error', [], 'loyality')]);
}
$em = $this->em;
$dataRegister = json_decode($RegisterRequest->getRequest(), true);
$userRegDTO = $userDTOFactory->createDefaultAuthDTO($dataRegister);
$user = $userEntityFactory->createUser($userRegDTO);
$user->setIsLoyalty(UserEntityFactory::LOYALTY_STATUS_DEFAULT);
$user->setEnabled(UserEntityFactory::ENABLED_STATUS);
$user->setRegistrationConfirm(UserEntityFactory::REGISTRATION_STATUS_CONFIRMED);
$user->setConfirmedPhone(UserEntityFactory::REGISTRATION_STATUS_CONFIRMED);
//TODO move to some config
if (!$user->hasRole('ROLE_LOYALTY_USER')) {
$user->addRole('ROLE_LOYALTY_USER');
}
if (!$user->getPhone()) {
$user->setPhone($phone);
}
$RegisterRequest->setIsComplete(1);
$RegisterRequest->setUser($user);
$em->persist($user);
$em->persist($RegisterRequest);
$em->flush();
$this->userModel->pushReferrer($request, $user, User::UTM_REGISTRATION);
}
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
try {
$validator->validateGetCodeData($data);
$authDTO = $DTOFactory->createWithLoginPropertyOnly($data);
} catch (DataNotValidException $e) {
$logger->error($e->getMessage(), $data);
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.error', [], 'loyality'),
]);
} catch (UserNotFoundException $e) {
$logger->critical($e->getMessage(), $data);
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.user_not_found', [], 'loyality'),
]);
}
$em = $this->em;
if (!$this->checkAttemptLogin($authDTO->user, $em, $authDTO->login, $authService)) {
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.access_denied', [], 'loyality'),
]);
}
$codes = $em->getRepository(Otp::class)
->findBy(['user' => $authDTO->user, 'state' => RegistrationStatusEnums::CREATED_STATE]);
// todo remove when realise what this number are
$now = new DateTime();
foreach ($codes as $code) {
//TODO move to config
if ($code->getDateCreate()->modify('+15 minutes') <= $now) {
/** @var Otp $code */
$code->setState(RegistrationStatusEnums::OUTDATED_STATE);
//$code->setState(3); todo remove when realise what this number are
$em->persist($code);
$em->flush();
continue;
}
$code->setState(RegistrationStatusEnums::USED_STATE);
//$code->setState(4); todo remove when realise what this number are
$em->persist($code);
$em->flush();
}
$resendCount = 0;
foreach ($codes as $code) {
if ($code->getDateCreate()->modify('+30 minutes') >= $now) {
$resendCount++;
}
}
if ($resendCount > RegistrationStatusEnums::MAX_AUTH_ATTEMPT) {
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.try_again_later', [], 'loyality')
]);
}
$otpEntity = $otpEntityFactory->createOtp($authDTO->user);
$authDTO->user->setOtp($otpEntity->getCode());
$authDTO->user->setOtpExpDate((new DateTime())->modify('+1 hour'));
$em->persist($otpEntity);
$em->persist($authDTO->user);
$em->flush();
if ($authDTO->user->getPhone() == null || strlen($authDTO->user->getPhone()) < User::MIN_LENGTH_PHONE) {
$message = $translator->trans(
'auth.phone_send_error',
['%phone%' => $this->getSecurePhone($authDTO->user->getPhone())],
'loyality'
);
return new JsonResponse([
'success' => false,
'field' => 'brokenPhone',
'error' => $message,
]);
}
$infobipSmsSender->sendSms(
$authDTO->user->getPhone(),
$translator->trans('auth.your_auth_code', ['%code%' => $otpEntity->getCode()], 'loyality')
);
return new JsonResponse([
'success' => true,
'phone' => $authDTO->user->getPhone(),
'message' => $translator->trans(
'auth.sending_to_phone_succeed',
['%phone%' => $this->getSecurePhone($authDTO->user->getPhone())],
'loyality'
),
'code' => $otpEntity->getId(),
]);
}
public function resendRegistrationCode(Request $request, OtpEntityFactory $otpEntityFactory,
TranslatorInterface $translator, InfobipSmsSender $infobipSmsSender): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$data = $request->request->all();
$requestId = $data['requestId'];
$RegisterRequest = $this->em->getRepository(RegisterRequest::class)->find($requestId);
//TODO move to config or service
$phone = substr(preg_replace('~[^0-9]+~', '', $data['phone']), -10);
if (!$RegisterRequest || $RegisterRequest->getPhone() != $phone) {
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.error', [], 'loyality'),
]);
}
$em = $this->em;
$codes = $em->getRepository(Otp::class)
->findBy(['request' => $RegisterRequest, 'state' => RegistrationStatusEnums::CREATED_STATE]);
$now = new DateTime();
foreach ($codes as $code) {
/** @var Otp $code */
//TODO move to config
if ($code->getDateCreate()->modify('+15 minutes') <= $now) {
$code->setState(RegistrationStatusEnums::OUTDATED_STATE);
$em->persist($code);
$em->flush();
continue;
}
$code->setState(RegistrationStatusEnums::USED_STATE);
$em->persist($code);
$em->flush();
}
$resendCount = 0;
foreach ($codes as $code) {
if ($code->getDateCreate()->modify('+30 minutes') >= $now) {
$resendCount++;
}
}
if ($resendCount > RegistrationStatusEnums::MAX_AUTH_ATTEMPT) {
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.try_again_later', [], 'loyality')
]);
}
$otpEntity = $otpEntityFactory->createOtpByRequest($RegisterRequest);
$RegisterRequest->setOtp($otpEntity->getCode());
$RegisterRequest->setOtpExpDate((new DateTime())->modify('+1 hour'));
$em->persist($otpEntity);
$em->persist($RegisterRequest);
$em->flush();
$infobipSmsSender->sendSms(
$RegisterRequest->getPhone(),
$translator->trans('auth.your_auth_code', ['%code%' => $otpEntity->getCode()], 'loyality')
);
return new JsonResponse([
'success' => true,
'phone' => $RegisterRequest->getPhone(),
'message' => $translator->trans(
'auth.sending_to_phone_succeed',
['%phone%' => $this->getSecurePhone($RegisterRequest->getPhone())],
'loyality'
),
'code' => $otpEntity->getId(),
]);
}
public function confirmRegistration(
Request $request,
UserEntityFactory $userEntityFactory,
RegUserDTOFactory $userDTOFactory,
TranslatorInterface $translator,
AuthService $authService
): JsonResponse
{
$requestId = $request->get('requestId');
$code = $request->get('code');
$phone = $request->get('phone');
//TODO move to config or service
$phone = substr(preg_replace('~[^0-9]+~', '', $phone), -10);
$RegisterRequest = $this->em->getRepository(RegisterRequest::class)->find($requestId);
if (!$RegisterRequest || $RegisterRequest->getPhone() != $phone) {
return new JsonResponse(['success' => false, 'error' => $translator->trans('auth.error', [], 'loyality')]);
}
$em = $this->em;
if ($RegisterRequest->getOtp() == $code && $RegisterRequest->getOtpExpDate() > (new DateTime)) {
$data = json_decode($RegisterRequest->getRequest(), true);
$userRegDTO = $userDTOFactory->createDefaultAuthDTO($data);
$user = $userEntityFactory->createUser($userRegDTO);
$user->setIsLoyalty(UserEntityFactory::LOYALTY_STATUS_DEFAULT);
$user->setEnabled(UserEntityFactory::ENABLED_STATUS);
$user->setRegistrationConfirm(UserEntityFactory::REGISTRATION_STATUS_CONFIRMED);
$user->setConfirmedPhone(UserEntityFactory::REGISTRATION_STATUS_CONFIRMED);
//TODO move to some config
if (!$user->hasRole('ROLE_LOYALTY_USER')) {
$user->addRole('ROLE_LOYALTY_USER');
}
if (!$user->getPhone()) {
$user->setPhone($phone);
}
$RegisterRequest->setIsComplete(1);
$RegisterRequest->setUser($user);
$em->persist($user);
$em->persist($RegisterRequest);
$em->flush();
$this->userModel->pushReferrer($request, $user, User::UTM_REGISTRATION);
return $authService->loginUser($user);
}
return new JsonResponse(['success' => false, 'error' => 'Не вiрний код']);
}
public function referralRegister(
Request $request,
OtpCodeGenerator $codeGenerator,
StringGenerator $stringGenerator,
TranslatorInterface $translator,
UserPasswordHasherInterface $encoder,
InfobipSmsSender $infobipSmsSender
): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$email = $request->get('email');
$password = $stringGenerator->generateUpperCaseAndNumbers();
$phone = $request->get('phone');
$users = $this->em->getRepository(User::class);
if ($users->checkRegisteredUsersByParams(['email' => $email])) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('register.email_already_in_use', [], 'loyality'),
]);
}
$user = $users->findByPhoneOrCardNum($phone);
if (!$user) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('register.user_register_problem', [], 'loyality'),
]);
}
/** @var User $user */
$referrerUser = $this->em
->getRepository(UserReferral::class)->findOneBy(['user_id' => $user->getId()]);
$em = $this->em;
if ($user && $user->getRegistrationConfirm()) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('register.user_already_register', [], 'loyality'),
]);
}
$otp = $codeGenerator->generate();
$user->setRegistrationConfirm(UserEntityFactory::REGISTRATION_STATUS_DEFAULT);
$user->setEmail($email);
$user->setEnabled(UserEntityFactory::ENABLED_STATUS);
$user->setIsLoyalty(UserEntityFactory::LOYALTY_STATUS_DEFAULT);
$user->setUsername($email);
$user->setEmailCanonical($email);
$user->setPassword($encoder->hashPassword($user, $password));
$user->setOtp($otp);
//TODO move to some config
$user->setOtpExpDate((new DateTime())->modify('+1 hour'));
$user->setReferrer($referrerUser->getPromoCode());
$user->setConfirmPhoneCod($otp);
$em->persist($user);
try {
$em->flush();
} catch (UniqueConstraintViolationException $ex) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('register.main_problem', [], 'loyality'),
]);
}
$infobipSmsSender->sendSms(
$phone,
$translator->trans('register.your_code_for_registration', ['%code%' => $otp], 'loyality')
);
return new JsonResponse([
'success' => true,
'message' => $translator->trans(
'auth.sending_to_phone_succeed',
['%phone%' => $this->getSecurePhone($phone)],
'loyality'
),
]);
}
public function referralRegisterConfirm(Request $request, AuthService $authService): JsonResponse
{
$code = $request->get('code');
$phone = $request->get('phone');
$em = $this->em;
/** @var User $user */
if (!($user = $this->em->getRepository(User::class)->findByPhoneOrCardNum($phone))) {
//TODO translate this
return new JsonResponse(
[
'success' => false,
'error' => 'Користувача не знайдено, зверніться до адміністратора'
]
);
}
$ReferralUser = $this->em->getRepository(UserReferral::class)
->findOneBy(['user_id' => $user->getId(), 'promo_code' => $user->getReferrer()]);
if (!$ReferralUser) {
//TODO translate this
return new JsonResponse(
[
'success' => false,
'error' => 'Користувача не знайдено, зверніться до адміністратора'
]
);
}
$ReferralUser->setStatus(UserReferral::STATUS_REGISTERED);
$em->persist($ReferralUser);
$em->flush();
if ($user->getOtp() == $code && $user->getOtpExpDate() > (new DateTime)) {
$user->setIsLoyalty(1);
$user->setEnabled(1);
$user->setRegistrationConfirm(1);
$user->setConfirmedPhone(1);
//TODO move to some config
if (!$user->hasRole('ROLE_LOYALTY_USER')) {
$user->addRole('ROLE_LOYALTY_USER');
}
if (!$user->getPhone()) {
$user->setPhone($phone);
}
$em->persist($user);
$em->flush();
$this->userModel->pushReferrer($request, $user, User::UTM_REGISTRATION);
return $authService->loginUser($user);
}
return new JsonResponse(['success' => false, 'error' => 'Не вірний код']);
}
public function recoveryConfirmCode(Request $request): JsonResponse
{
$codeId = $request->get('code');
$userCode = $request->get('userCode');
$user = null;
$login = $request->get('email');
if ($request->get('loginPhone') === 'true') {
$login = $this->preparePhone($login);
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum($login, null, null);
} else {
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum(null, null, $login);
}
$userData = null;
//TODO translate this
if (!$user) {
return new JsonResponse([
'success' => false,
'error' => 'Користувача з таким email та паролем не знайдено',
]);
} else {
$userData = $this->em->getRepository(User::class)->find($user);
}
$em = $this->em;
$code = $em->getRepository(Otp::class)->find($codeId);
if (!$code || $code->getUser() != $user) {
//TODO translate this
return new JsonResponse([
'success' => false,
'error' => 'Ваш код прострочений, будь ласка, отримаєте новий код'
]);
}
if ($userCode != $code->getCode() || $userCode != $userData->getOtp()) {
//TODO translate this
return new JsonResponse([
'success' => false,
'error' => 'Невірний код'
]);
}
if ($userData->getOtpExpDate() < (new DateTime)) {
//TODO translate this
return new JsonResponse([
'success' => false,
'error' => 'Термін дії коду закінчився. Вам необхідно запросити новий код відновлення',
]);
}
return new JsonResponse(['success' => true]);
}
public function codeLogin(Request $request, TranslatorInterface $translator, AuthService $authService): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$user = $this->getUser();
if ($user) {
$authService->logoutUser($user);
}
return $authService->verifyAuthUser();
}
public function codeLoginNewPhone(Request $request, TranslatorInterface $translator, AuthService $authService): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$user = $this->getUser();
if ($user) {
$authService->logoutUser($user);
}
return $authService->verifyAuthNewPhoneUser();
}
public function getCodeRecovery(Request $request, TranslatorInterface $translator, InfobipSmsSender $infobipSmsSender): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$user = null;
$login = $request->get('email');
if ($request->get('loginPhone') === 'true') {
$login = $this->preparePhone($login);
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum($login, null, null);
} else {
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum(null, null, $login);
}
$userData = null;
if (!$user) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('recovery.user_not_found', [], 'loyality')
]);
} else {
//WTF??
$userData = $this->em->getRepository(User::class)->find($user);
}
$em = $this->em;
$codes =
$em->getRepository(Otp::class)
->findBy(['user' => $user, 'state' => RegistrationStatusEnums::CREATED_STATE]);
foreach ($codes as $code) {
$now = new DateTime();
if ($code->getDateCreate()->modify('+15 minutes') <= $now) {
// $code->setState(3);
$code->setState(RegistrationStatusEnums::OUTDATED_STATE);
$em->persist($code);
continue;
}
// $code->setState(4);
$code->setState(RegistrationStatusEnums::USED_STATE);
$em->persist($code);
}
$em->flush();
$resendCount = 0;
foreach ($codes as $code) {
$now = new DateTime();
//TODO move to some config
if ($code->getDateCreate()->modify('+30 minutes') >= $now) {
$resendCount++;
}
}
//TODO move to some config
if ($resendCount > RegistrationStatusEnums::MAX_AUTH_ATTEMPT) {
//TODO translate this
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.try_again_later', [], 'loyality')
]);
}
//TODO move to some config or use service
$otpRandCode = random_int(111111, 999999);
$userData->setOtp($otpRandCode);
$userData->setOtpExpDate((new DateTime())->modify('+1 hour'));
$em->persist($userData);
$em->flush();
$otpEntity = new Otp();
$otpEntity->setCode($otpRandCode);
$otpEntity->setState(RegistrationStatusEnums::CREATED_STATE);
$otpEntity->setDateCreate(new DateTime());
$otpEntity->setUser($user);
$otpEntity->setPhone($userData->getPhone());
$em->persist($otpEntity);
$em->flush();
//TODO translate this
$infobipSmsSender->sendSms($userData->getPhone(), 'Ваш код для відновлення: ' . $otpEntity->getCode());
//TODO translate this
$message =
'Ми відправили вам за номером телефону ' .
$this->getSecurePhone($userData->getPhone()) .
' або в viber тимчасовий пароль';
return new JsonResponse([
'success' => true,
'message' => $message,
'code' => $otpEntity->getId()
]);
}
public function setRecoveryPassword(Request $request, TranslatorInterface $translator,
UserPasswordHasherInterface $encoder, AuthService $authService): JsonResponse
{
$recoveryPassword = $request->get('recoveryPassword');
$codeId = $request->get('code');
$userCode = $request->get('userCode');
$user = null;
$login = $request->get('email');
if ($request->get('loginPhone') === 'true') {
$login = $this->preparePhone($login);
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum($login, null, null);
} else {
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum(null, null, $login);
}
if (!$user) {
return new JsonResponse(['success' => false, 'error' => 'Виникла помилка']);
}
$userData = $this->em->getRepository(User::class)->find($user);
$em = $this->em;
$code = $em->getRepository(Otp::class)->find($codeId);
if (!$code || $code->getUser() != $user) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('auth.error_old_code', [], 'loyality'),
]);
}
if ($userCode != $code->getCode() || $userCode != $userData->getOtp()) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('auth.error_code', [], 'loyality'),
]);
}
if ($userData->getOtpExpDate() < (new DateTime)) {
return new JsonResponse([
'success' => false,
'error' => $translator->trans('auth.error_old_code', [], 'loyality'),
]);
}
$userData->setPassword($encoder->hashPassword($user, $recoveryPassword));
$em->persist($userData);
$em->flush();
return $authService->loginUser($user);
}
public function sendRecoverEmail(Request $request, RouterInterface $router): JsonResponse
{
$email = $request->request->get('email');
$userModel = $this->userModel;
if (!$userModel->initByEmail($email)) {
$regUrl = $router->generate('my_register_page');
return $this->errorResponce('Користувача з таким E-mail не знайдено. <br>' .
'Перевірте адресу або <a href="' . $regUrl . '" class="upper-reg">зареєструйтесь</a>.');
}
$userModel->sendRestoreEmail();
return new JsonResponse(['success' => true]);
}
public function checkUser()
{
return $this->baseMyRender('@My/Auth/aith-iframe.html.twig');
}
public function crossDomainAuth(): JsonResponse
{
$User = $this->getUser();
if (
!$User ||
!(
//TODO remove to som config path
$User->hasRole('ROLE_LOYALTY_USER') ||
$User->hasRole('ROLE_SUPER_ADMIN') ||
$User->hasRole('ROLE_DC_MANAGER') ||
$User->hasRole('ROLE_CONTENT_MANAGER')
)
) {
$Response = new JsonResponse([
'token' => false
]);
return $Response;
}
//TODO remove to som config path
$token = md5(time() . 'myvidisaltD@##@$GFQSF' . $User->getId());
$AuthToken = new CrossDomainAuthToken();
$AuthToken->setDateCreate(new DateTime());
$AuthToken->setToken($token);
$AuthToken->setUser($User);
$this->em->persist($AuthToken);
$this->em->flush();
$Response = new JsonResponse([
'token' => $token
]);
return $Response;
}
public function getCodeChangePhone(Request $request, OtpEntityFactory $otpEntityFactory,
TranslatorInterface $translator, MailerInterface $mailer): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$email = $request->get('email');
$user = $this->em
->getRepository(User::class)
->findByPhoneOrCardNum(null, null, $email);
if (!$user) {
return new JsonResponse([
'success' => false,
'field' => 'email',
'error' => $translator->trans('change_phone.user_not_found', [], 'loyality')
]);
}
$codes = $this->em->getRepository(Otp::class)
->findBy(['user' => $user, 'state' => RegistrationStatusEnums::CREATED_STATE]);
foreach ($codes as $code) {
$now = new DateTime();
if ($code->getDateCreate()->modify('+15 minutes') <= $now) {
$code->setState(RegistrationStatusEnums::OUTDATED_STATE);
$this->em->persist($code);
continue;
}
$code->setState(RegistrationStatusEnums::USED_STATE);
$this->em->persist($code);
}
$this->em->flush();
$resendCount = 0;
foreach ($codes as $code) {
$now = new DateTime();
if ($code->getDateCreate()->modify('+30 minutes') >= $now) {
$resendCount++;
}
}
if ($resendCount > RegistrationStatusEnums::MAX_AUTH_ATTEMPT) {
return new JsonResponse([
'success' => false,
'field' => 'other',
'error' => $translator->trans('auth.try_again_later', [], 'loyality')
]);
}
$otpEntity = $otpEntityFactory->createOtp($user);
$user->setOtp($otpEntity->getCode());
$user->setOtpExpDate((new DateTime())->modify('+1 hour'));
$this->em->persist($otpEntity);
$this->em->persist($user);
$this->em->flush();
$mailMessage = $translator->trans('change_phone.mail_message', [], 'loyality');
$mailBody = $this->twig->render('@My/Mail/change_phone_email.html.twig', [
'user' => $user,
'message' => $mailMessage
]);
$message = (new Email())
->subject($translator->trans('change_phone.mail_subject', [], 'loyality'))
->from(new Address('info@vidi.ua', $translator->trans('change_phone.mail_name_from', [], 'loyality')))
->html($mailBody)
->to($email);
$mailer->send($message);
return new JsonResponse([
'success' => true,
'message' => $translator->trans(
'change_phone.email_sending_to',
['%email%' => $user->getEmail()],
'loyality'
),
'code' => $otpEntity->getId()
]);
}
public function setNewPhone(
Request $request,
OtpEntityFactory $otpEntityFactory,
AuthDataValidator $validator,
LoggerInterface $logger,
AuthCodeDTOFactory $DTOFactory,
TranslatorInterface $translator,
InfobipSmsSender $infobipSmsSender
): JsonResponse
{
$translator->setLocale($request->getLocale());
if ($request->request->has('locale')) {
$translator->setLocale($request->get('locale'));
}
$code = $request->get('userCode');
$data = $request->request->all();
try {
$validator->validateForSetNewPhone($data);
$authDTO = $DTOFactory->createWithEmailAndPhoneProperties($data);
} catch (DataNotValidException|UserNotFoundException $e) {
$logger->error('auth.error', $data);
return new JsonResponse([
'success' => false,
'error' => $translator->trans('auth.error', [], 'loyality')
]);
}
if ($authDTO->user->getOtp() != $code || $authDTO->user->getOtpExpDate() < (new DateTime)) {
return new JsonResponse([
'success' => false,
'field' => 'code',
'error' => $translator->trans('auth.error_code', [], 'loyality'),
]);
}
if (
$this->em
->getRepository(User::class)
->findByPhoneOrCardNum($authDTO->phone, null, null)
) {
return new JsonResponse([
'success' => false,
'field' => 'phone',
'error' => $translator->trans('change_phone.not_unique_phone', [], 'loyality'),
]);
}
//set new phone to user
$authDTO->user->setPhoneNew($authDTO->phone);
$em = $this->em;
$usedOtp = $em->getRepository(Otp::class)->findOneBy(['user' => $authDTO->user], ['id' => 'DESC']);
//outdated current otp to avoid user block
$usedOtp->setState(RegistrationStatusEnums::OUTDATED_STATE);
//new OTP for sending to new user phone
$otpEntity = $otpEntityFactory->createOtpPhoneNew($authDTO->user);
$authDTO->user->setOtp($otpEntity->getCode());
$authDTO->user->setOtpExpDate((new DateTime())->modify('+1 hour'));
$em->persist($usedOtp);
$em->persist($authDTO->user);
$em->persist($otpEntity);
$em->flush();
$infobipSmsSender->sendSms(
$authDTO->user->getPhoneNew(),
$translator->trans('auth.your_auth_code', ['%code%' => $otpEntity->getCode()], 'loyality')
);
return new JsonResponse([
'success' => true,
'code' => $otpEntity->getId(),
'email' => $authDTO->user->getEmail(),
]);
}
private function preparePhone($phone)
{
return substr(preg_replace('~[^0-9]+~', '', $phone), -10);
}
private function checkAttemptLogin(User $user, EntityManagerInterface $em, $login, AuthService $authService): bool
{
$lastAttempt = $em->getRepository(AttemptLogin::class)
->findOneBy(['user' => $user], ['id' => 'DESC']);
if ($lastAttempt && $lastAttempt->getState()) {
$dateBlocked = $lastAttempt->getCreatedAt()->modify('5 minutes');
if ($dateBlocked->getTimestamp() < (new DateTime)->getTimestamp()) {
$authService->clearHistoryAttemptLogin($user);
}
}
$attempts = $em->getRepository(AttemptLogin::class)
->findBy(['user' => $user]);
if (count($attempts) < RegistrationStatusEnums::MAX_AUTH_ATTEMPT_BEFORE_BLOCK) {
$attemptLogin = new AttemptLogin();
$attemptLogin->setCreatedAt(new DateTime());
$attemptLogin->setLogin($login);
$attemptLogin->setState(RegistrationStatusEnums::CREATED_STATE);
$attemptLogin->setUser($user);
$em->persist($attemptLogin);
$em->flush();
return true;
}
$lastAttempt->setState(RegistrationStatusEnums::ACTIVE_STATE);
$em->persist($lastAttempt);
$em->flush();
return false;
}
private function errorResponce($errors): JsonResponse
{
return new JsonResponse(['success' => false, 'errors' => $errors]);
}
private function getSecurePhone($phone): string
{
//TODO move to config or service
return '+380...' . mb_substr($phone, -4, 6);
}
}