Files
clinicpro/src/Auth/Security/PasswordAuthenticator.php
T

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);
}
}