feat: Implement SMS sending functionality with KavehNegar and Rangineh providers
- Add SendSmsMessage class for encapsulating SMS message data. - Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS. - Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates. - Develop SendSmsHandler for handling SMS sending messages. - Create SmsService to manage SMS dispatching and logging. - Add UserProfileController for managing user profiles with CRUD operations. - Implement UserProfile entity and repository for user profile data management. - Update symfony.lock and bootstrap.php for project dependencies and environment setup.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<?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' => 3600,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user