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,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Auth\Controller;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\Auth\Repository\UserRepository;
|
||||
use App\Auth\Service\OtpService;
|
||||
use App\Auth\Service\TokenService;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepo,
|
||||
private readonly OtpService $otpService,
|
||||
private readonly TokenService $tokenService,
|
||||
private readonly RateLimiterFactory $sendCodeLimiter,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Route exists so the router resolves it; PasswordAuthenticator intercepts
|
||||
* and returns the JWT response before this controller body ever runs.
|
||||
*/
|
||||
#[Route('/api/v1/user/login', methods: ['POST'])]
|
||||
public function login(): JsonResponse
|
||||
{
|
||||
return $this->error(ErrorCodes::ERR_AUTH_005, ErrorCodes::message(ErrorCodes::ERR_AUTH_005), 401);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/user/send-code', methods: ['POST'])]
|
||||
public function sendCode(Request $request): JsonResponse
|
||||
{
|
||||
$limiter = $this->sendCodeLimiter->create($request->getClientIp() ?? 'unknown');
|
||||
if (!$limiter->consume(1)->isAccepted()) {
|
||||
return $this->error(ErrorCodes::ERR_RATE_LIMIT_001, ErrorCodes::message(ErrorCodes::ERR_RATE_LIMIT_001), 429);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$mobile = trim($data['mobile'] ?? '');
|
||||
|
||||
if (!preg_match('/^09[0-9]{9}$/', $mobile)) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'فرمت شماره موبایل نادرست است', 422, 'mobile');
|
||||
}
|
||||
|
||||
$uuid = $this->otpService->sendCode($mobile);
|
||||
|
||||
return new JsonResponse(['uuid' => $uuid, 'message' => 'کد تایید با موفقیت ارسال شد.']);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/user/verify-code', methods: ['POST'])]
|
||||
public function verifyCode(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$uuid = trim($data['uuid'] ?? '');
|
||||
$code = trim($data['code'] ?? '');
|
||||
|
||||
if (empty($uuid) || empty($code)) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'uuid و code الزامی است', 422);
|
||||
}
|
||||
|
||||
$this->otpService->verifyCode($uuid, $code);
|
||||
|
||||
return $this->success(['message' => 'کد با موفقیت تایید شد.']);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/user/register', methods: ['POST'])]
|
||||
public function register(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$uuid = trim($data['uuid'] ?? '');
|
||||
$realName = trim($data['real_name'] ?? '');
|
||||
|
||||
if (empty($uuid)) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'uuid الزامی است', 422);
|
||||
}
|
||||
|
||||
$otpData = $this->otpService->getVerifiedOtpData($uuid);
|
||||
$mobile = $otpData['mobile'];
|
||||
|
||||
$user = $this->userRepo->findByMobile($mobile) ?? new User($mobile);
|
||||
if ($realName !== '') {
|
||||
$user->setRealName($realName);
|
||||
}
|
||||
|
||||
$this->userRepo->save($user);
|
||||
$this->otpService->deleteOtp($uuid);
|
||||
|
||||
return $this->success(['message' => 'ثبتنام با موفقیت انجام شد.', 'uuid' => $user->getUuid()], 201);
|
||||
}
|
||||
|
||||
#[Route('/oauth/token', methods: ['POST'])]
|
||||
public function issueToken(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$grant = $data['grant_type'] ?? '';
|
||||
$uuid = trim($data['uuid'] ?? '');
|
||||
|
||||
if ($grant !== 'mobile') {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'grant_type نامعتبر است', 400);
|
||||
}
|
||||
|
||||
$otpData = $this->otpService->getVerifiedOtpData($uuid);
|
||||
$mobile = $otpData['mobile'];
|
||||
|
||||
$user = $this->userRepo->findByMobile($mobile) ?? new User($mobile);
|
||||
$this->userRepo->save($user);
|
||||
$this->otpService->deleteOtp($uuid);
|
||||
|
||||
return new JsonResponse($this->tokenService->issueTokens($user));
|
||||
}
|
||||
|
||||
#[Route('/oauth/token/refresh', methods: ['POST'])]
|
||||
public function refreshToken(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$refreshToken = trim($data['refresh_token'] ?? '');
|
||||
|
||||
if (empty($refreshToken)) {
|
||||
return $this->error(ErrorCodes::ERR_AUTH_001, 'refresh_token الزامی است', 401);
|
||||
}
|
||||
|
||||
$result = $this->tokenService->refreshToken($refreshToken);
|
||||
$user = $this->userRepo->find($result['userId']);
|
||||
|
||||
if ($user === null) {
|
||||
return $this->error(ErrorCodes::ERR_AUTH_001, ErrorCodes::message(ErrorCodes::ERR_AUTH_001), 401);
|
||||
}
|
||||
|
||||
$tokens = $this->tokenService->issueTokens($user);
|
||||
$tokens['refresh_token'] = $result['rawToken'];
|
||||
|
||||
return new JsonResponse($tokens);
|
||||
}
|
||||
|
||||
#[Route('/oauth/userinfo', methods: ['GET'])]
|
||||
public function userInfo(#[CurrentUser] ?User $user): JsonResponse
|
||||
{
|
||||
if ($user === null) {
|
||||
return $this->error(ErrorCodes::ERR_AUTH_001, ErrorCodes::message(ErrorCodes::ERR_AUTH_001), 401);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'id' => $user->getId(),
|
||||
'uuid' => $user->getUuid(),
|
||||
'mobile_number' => $user->getMobileNumber(),
|
||||
'realName' => $user->getRealName(),
|
||||
'status' => $user->getStatus(),
|
||||
'roles' => $user->getRoles(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/oauth/logout', methods: ['POST'])]
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$refreshToken = trim($data['refresh_token'] ?? '');
|
||||
|
||||
if ($refreshToken !== '') {
|
||||
$this->tokenService->revokeRefreshToken($refreshToken);
|
||||
}
|
||||
|
||||
return $this->success(['message' => 'خروج با موفقیت انجام شد']);
|
||||
}
|
||||
|
||||
#[Route('/session/token', methods: ['GET'])]
|
||||
public function sessionToken(): JsonResponse
|
||||
{
|
||||
return new JsonResponse(['token' => bin2hex(random_bytes(16))]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user