106 lines
4.1 KiB
PHP
106 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Auth\Security;
|
|
|
|
use App\Auth\Repository\UserRepository;
|
|
use App\Shared\Constant\ErrorCodes;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
|
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
|
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
|
use Symfony\Contracts\Cache\CacheInterface;
|
|
|
|
class PasswordAuthenticator extends AbstractAuthenticator
|
|
{
|
|
public function __construct(
|
|
private readonly UserRepository $userRepository,
|
|
private readonly JWTTokenManagerInterface $jwtManager,
|
|
private readonly CacheInterface $cache,
|
|
private readonly LoggerInterface $logger,
|
|
private readonly RateLimiterFactory $loginLimiter,
|
|
private readonly int $refreshTokenTtl = 2592000,
|
|
) {}
|
|
|
|
public function supports(Request $request): ?bool
|
|
{
|
|
return $request->getPathInfo() === '/api/v1/user/login'
|
|
&& $request->isMethod('POST');
|
|
}
|
|
|
|
public function authenticate(Request $request): Passport
|
|
{
|
|
$limiter = $this->loginLimiter->create($request->getClientIp() ?? 'unknown');
|
|
if (!$limiter->consume(1)->isAccepted()) {
|
|
throw new TooManyRequestsHttpException(60, 'تعداد تلاشهای ورود از حد مجاز گذشت');
|
|
}
|
|
|
|
$data = json_decode($request->getContent(), true) ?? [];
|
|
$mobile = trim($data['mobile_number'] ?? '');
|
|
$pass = $data['password'] ?? '';
|
|
|
|
return new Passport(
|
|
new UserBadge($mobile, fn(string $id) => $this->userRepository->findByMobile($id)),
|
|
new PasswordCredentials($pass)
|
|
);
|
|
}
|
|
|
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
|
{
|
|
$user = $token->getUser();
|
|
|
|
if (!$user->isStaff()) {
|
|
return new JsonResponse([
|
|
'success' => false,
|
|
'data' => null,
|
|
'errors' => [['code' => ErrorCodes::ERR_AUTH_006, 'message' => ErrorCodes::message(ErrorCodes::ERR_AUTH_006)]],
|
|
], 403);
|
|
}
|
|
|
|
$accessToken = $this->jwtManager->create($user);
|
|
$rawToken = bin2hex(random_bytes(32));
|
|
$cacheKey = 'refresh_' . hash('sha256', $rawToken);
|
|
|
|
$item = $this->cache->getItem($cacheKey);
|
|
$item->set((string) $user->getId());
|
|
$item->expiresAfter($this->refreshTokenTtl);
|
|
$this->cache->save($item);
|
|
|
|
$this->logger->info('login_success', [
|
|
'user_id' => $user->getId(),
|
|
'ip' => $request->getClientIp(),
|
|
'method' => 'password',
|
|
]);
|
|
|
|
return new JsonResponse([
|
|
'access_token' => $accessToken,
|
|
'refresh_token' => $rawToken,
|
|
'token_type' => 'Bearer',
|
|
'expires_in' => 900,
|
|
'refresh_token_expires_in' => $this->refreshTokenTtl,
|
|
]);
|
|
}
|
|
|
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
|
|
{
|
|
$this->logger->warning('login_failed', [
|
|
'ip' => $request->getClientIp(),
|
|
'reason' => $exception->getMessage(),
|
|
]);
|
|
|
|
return new JsonResponse([
|
|
'success' => false,
|
|
'data' => null,
|
|
'errors' => [['code' => ErrorCodes::ERR_AUTH_005, 'message' => ErrorCodes::message(ErrorCodes::ERR_AUTH_005)]],
|
|
], 401);
|
|
}
|
|
}
|