<?php
declare(strict_types=1);
namespace App\Controller;
use App\Logger\Log;
use App\Form\AuthForm;
use App\Entity\BaseAuth;
use App\Form\CommonForm;
use App\Service\AuthService;
use App\Service\FileService;
use App\Service\EmailService;
use App\Service\IAuthService;
use App\Service\IEmailService;
use App\Service\OptionService;
use App\Service\SchoolService;
use App\Service\IOptionService;
use Symfony\Component\Asset\Packages;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class AuthController extends BaseController
{
private AuthForm $authForm;
public function __construct(
AuthForm $authForm,
AuthService $authService,
EmailService $notifyService,
OptionService $optionService,
SchoolService $schoolService,
Security $security,
FileService $fileService,
SessionInterface $session,
Packages $assetsManager,
TranslatorInterface $translator,
Log $log
) {
parent::__construct(
$authService,
$notifyService,
$optionService,
$fileService,
$session,
$assetsManager,
$translator,
$log,
$security,
$schoolService
);
$this->authForm = $authForm;
}
/**
* @Route("/login", name="auth_signin")
*/
public function signIn(Request $request, AuthenticationUtils $authenticationUtils, ParameterBagInterface $parameter): Response
{
$lastUserName = $authenticationUtils->getLastUsername();
$lastError = $authenticationUtils->getLastAuthenticationError();
$view ='auth/sign_in.html.twig';
if($parameter->get('app.user') == IAuthService::ROLE_FAMILY){
$view ='auth/sign_family.html.twig';
}
return $this->render($view, [
'lastUser' => $lastUserName,
'lastError' => $lastError,
'env' => $parameter->get('app.env')
]);
}
/**
* @Route("/forgot", name="auth_forgot_password")
*/
public function forgotPassword(Request $request, CsrfTokenManagerInterface $csrfTokenManager): Response
{
if ($request->isMethod('POST') && $request->isXmlHttpRequest()) {
$token = new CsrfToken('form_forgot_password', $request->request->get('token'));
if ($csrfTokenManager->isTokenValid($token)) {
$result = $this->authForm->checkForgot($request, $this->session);
if ($result instanceof BaseAuth) {
$this->notifyService->sendMail(
$this->translator->trans('activation_code'),
'forgot_password',
[
'email' => $result->getEmail(),
'data' => [
'fullname' => $result->getFullName(),
'code' => $result->getValidationCode(),
'time_elapsed' => IOptionService::MAX_ACCOUNT_ACTIVATION_TIME,
'app_url' => IOptionService::BASE_URL,
'support_mail' => IEmailService::SUPPORT,
'logo' => IOptionService::CDN_LOGO,
'favicon' => IOptionService::CDN_FAVICON,
],
]
);
return new JsonResponse(null, 200);
} else if(is_array($result)){
return new JsonResponse($result, 400);
}
return new JsonResponse(null, 200);
}
return new JsonResponse(null, 200);
}
return $this->render('auth/forgot_password.html.twig');
}
/**
* @Route("/activation", name="auth_activation")
*/
public function activation(Request $request, CsrfTokenManagerInterface $csrfTokenManager): Response
{
$account = $this->session->get(IOptionService::SESSION_ACCOUNT);
if (null == $account || !in_array($account['step'], ['signin_staff_activation', 'signup_activation', 'forgot_activation', 'signin_activation'])) {
$this->activityLog($request,get_class($this),__FUNCTION__,'activation url error not in array',Log::MESSAGE_INFO,'HACK');
$this->session->invalidate();
return $this->redirectToRoute('auth_signout');
}
$referer = $request->headers->get("referer"); // get the referer, it can be empty!
if (!\is_string($referer) || !$referer) {
$this->activityLog($request,get_class($this),__FUNCTION__,'activation with no referrer',Log::MESSAGE_INFO,'HACK',$account["email"]);
$this->session->invalidate();
return $this->redirectToRoute("auth_signout");
}
$title = $message = $step = null;
if ('signin_activation' == $account['step']) {
$title = $this->translator->trans('activation_code');
$message = $this->translator->trans('signup_activation_code_info1');
$step = 'signin_activation';
} elseif ('forgot_activation' == $account['step']) {
$title = $this->translator->trans('activation_code');
$message = $this->translator->trans('forgot_activation_code_info2', ['%0%' => CommonForm::maskEmail($account['email'])]);
$step = 'forgot_activation';
}
if ($request->isMethod("POST") && $request->isXmlHttpRequest()) {
$token = new CsrfToken("activation_form", $request->request->get("token"));
if ($csrfTokenManager->isTokenValid($token)) {
$jsonData = $this->authForm->checkActivation($request, $this->session, $this->log, $this->getUser());
if ($jsonData != null) {
if ($account["step"] == "signin_activation") {
if ($jsonData == IOptionService::RESPONSE_AUTH) {
//clean session
$this->session->remove(IOptionService::SESSION_ACCOUNT);
//create new session file
$this->fileService->createSessionFile(
"app.connection_dir",
$account["email"],
$request->getSession()->getId(),
(new \DateTime())->format("Y-m-d")
);
}
return new JsonResponse($jsonData, 200);
}
if ($account["step"] == "forgot_activation") {
return new JsonResponse($jsonData, 200);
}
}
}
return new JsonResponse($this->translator->trans('wrong_activation_code'), 200);
}
return $this->render(
"auth/activation.html.twig",
[
"title" => $title,
"message" => $message,
"step" => $step
]
);
}
/**
* @Route("/resend-activation-code", name="auth_resent_activation_code")
*/
public function resendActivationCode(Request $request, EntityManagerInterface $manager): Response
{
$account = $this->session->get(IOptionService::SESSION_ACCOUNT);
if (null == $account || !in_array($account['step'], ['signin_staff_activation', 'signup_activation', 'forgot_activation', 'signin_activation'])) {
$this->activityLog($request,get_class($this),__FUNCTION__,'activation url error not in array',Log::MESSAGE_INFO,'HACK','UNKNOWN');
$this->session->invalidate();
return new JsonResponse(['redirect' => $this->generateUrl('auth_signout')], 400);
}
$referer = $request->headers->get("referer"); // get the referer, it can be empty!
if (!\is_string($referer) || !$referer) {
$this->activityLog($request,get_class($this),__FUNCTION__,'activation with no referrer',Log::MESSAGE_INFO,'HACK',$account["email"]);
$this->session->invalidate();
return new JsonResponse(['redirect' => $this->generateUrl('auth_signout')], 400);
}
try {
// Generate activation code
$activationCode = null;
$page = 'signin_activation' == $account['step'] ? IAuthService::ACTIVATION : IAuthService::RESET;
$findUser = $this->authService->signInUser($account['email'], null);
if (!empty($findUser)) {
$activationCode = CommonForm::generateCode(6, true);
$findUser->setValidationCode($activationCode);
$findUser->setValidationPage($page);
$findUser->setValidationDate(new \DateTime('now'));
$manager->persist($findUser);
$manager->flush();
$account['activation'] = $activationCode;
$account['created'] = new \DateTime('now');
$this->session->set(IOptionService::SESSION_ACCOUNT, $account);
$this->notifyService->sendMail(
$this->translator->trans('request_activation_code'),
'request_activation_code',
[
'email' => $findUser->getEmail(),
'data' => [
'fullname' => $findUser->getFullName(),
'code' => $account['activation'],
'time_elapsed' => IOptionService::MAX_ACCOUNT_ACTIVATION_TIME,
'app_url' => IOptionService::BASE_URL,
'support_mail' => IEmailService::SUPPORT,
'logo' => IOptionService::CDN_LOGO,
'favicon' => IOptionService::CDN_FAVICON,
],
]
);
$this->activityLog($request,get_class($this),__FUNCTION__,'User ask again activation code success',Log::MESSAGE_ERROR);
return new JsonResponse(['message' => $this->translator->trans('activation_code_resend')], 200);
}
$this->activityLog($request,get_class($this),__FUNCTION__,'account not found',Log::MESSAGE_ERROR,'HACK','UNKNOWN');
return new JsonResponse(['message' => $this->translator->trans('activation_code_resend')], 200);
} catch (\Exception $e) {
$this->activityLog($request,get_class($this),__FUNCTION__,'User ask again activation code failed'.$account['email']
,Log::MESSAGE_ERROR);
return new JsonResponse(['message' => $this->translator->trans('activation_code_resend')], 200);
}
}
/**
* @Route("/reset-password", name="auth_reset_password")
*/
public function resetPassword(Request $request, CsrfTokenManagerInterface $csrfTokenManager): Response
{
$account = $this->session->get(IOptionService::SESSION_ACCOUNT);
if (empty($account)) {
return $this->redirectToRoute('auth_signin');
}
if ($request->isMethod('POST') && $request->isXmlHttpRequest()) {
$token = new CsrfToken('reset_form', $request->request->get('token'));
if ($csrfTokenManager->isTokenValid($token)) {
$result = $this->authForm->checkReset($request, $this->session);
if ($result instanceof BaseAuth) {
// clean session
$this->session->remove(IOptionService::SESSION_ACCOUNT);
// delete blacklist file
$this->fileService->deleteFile($this->getParameter('app.blacklist_dir').DIRECTORY_SEPARATOR.$result->getEmail().'.log');
return new JsonResponse(null, 200);
}
if ('fake_email' == $result) {
return new JsonResponse(null, 200);
}
return new JsonResponse(null, 200);
}
return new JsonResponse(null, 200);
}
return $this->render('auth/reset.html.twig');
}
/**
* @Route("/validate-account/{tid}", name="auth_validate_account")
*/
public function validate(Request $request, CsrfTokenManagerInterface $csrfTokenManager, ?bool $profileId = false): Response
{
if ($this->fileService->countLinesInFile($this->getParameter('app.blacklist_dir').DIRECTORY_SEPARATOR.CommonForm::getAddressIp($request->getClientIp())) >= 3) {
return $this->render('error/error.html.twig', ['url' => IOptionService::BASE_URL]);
}
if ($request->isMethod('POST') && $request->isXmlHttpRequest()) {
$token = new CsrfToken('validate_account', $request->request->get('token'));
if ($csrfTokenManager->isTokenValid($token)) {
$response = $this->authForm->checkValidateAccount($request);
if (empty($response)) {
$this->session->invalidate();
return new JsonResponse([[
'message' => $this->translator->trans('account_validation_success'),
'redirect' => $this->generateUrl('auth_signin'),],
], 200);
}
return new JsonResponse([['message' => $response,],], 400);
}
return new JsonResponse([[
'message' => $this->translator->trans('account_validation_success'),
'redirect' => $this->generateUrl('auth_signin'),],
], 200);
}
$findUser = $this->authService->loadUserByValidationCode($request->attributes->get('tid'));
if(empty($findUser) || ($findUser instanceof BaseAuth && ($findUser->getStatus() != IOptionService::STATUS_PENDING))) {
// write in blacklist file
$this->fileService->appendFile(
"app.blacklist_dir",
CommonForm::getAddressIp($request->getClientIp()),
"date=" . (new \DateTime())->format("Y-m-d H:i:s") . "|ip=" . CommonForm::getAddressIp($request->getClientIp()) . "|page=CREATE_ACCOUNT|user-agent=" . $request->headers->get("user-agent") . PHP_EOL
);
$this->activityLog($request,get_class($this),__FUNCTION__,'invalid request: attempt to get new account',Log::MESSAGE_ERROR,'HACK');
return $this->render("error/error.html.twig", ["url" => IOptionService::BASE_URL]);
}
return $this->render('auth/validate_account.html.twig', [
'entity' => $findUser,
'tid' => $request->attributes->get('tid'),
]);
}
/**
* @Route("/signout", name="auth_signout")
*/
public function signout(Request $request): Response
{
$request->getSession()->invalidate(1);
$request->getSession()->clear();
return $this->redirectToRoute('auth_signin');
}
}