- 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.
4.8 KiB
4.8 KiB
نکات پیادهسازی — تسک ۰۲: ماژول احراز هویت
مهمترین تفاوت با طراحی اولیه
جریان OTP با UUID (نه موبایل)
در Drupal، کد OTP با UUID ذخیره میشود، نه با شماره موبایل:
// ساختار ذخیرهسازی در KeyValue/Redis:
key = uuid (تولیدشده در send-code)
value = { "code": "12345", "mobile": "09120671713" }
TTL = 1200 ثانیه
verify-code و oauth/token هر دو uuid میخواهند، نه موبایل.
// OtpService.php
public function generate(string $mobile): array
{
$uuid = Uuid::uuid4()->toString();
$code = $this->isDev() ? '12345' : (string) random_int(10000, 99999);
$this->redis->setex("otp:{$uuid}", 1200, json_encode([
'code' => $code,
'mobile' => $mobile,
]));
return ['uuid' => $uuid, 'code' => $code];
}
public function verify(string $uuid, string $code): bool
{
$data = json_decode($this->redis->get("otp:{$uuid}"), true);
if (!$data || $data['code'] !== $code) return false;
$this->redis->del("otp:{$uuid}");
return true;
}
public function getMobileByUuid(string $uuid): ?string
{
$data = json_decode($this->redis->get("otp:{$uuid}"), true);
return $data['mobile'] ?? null;
}
MobileGrant در Symfony
به جای OAuth2 کامل، یک custom JWT grant پیادهسازی کن:
POST /oauth/token
grant_type=mobile
uuid=...
code=...
client_id=clinic-pro
client_secret=...
→ OtpService::verify(uuid, code) تأیید کند
→ getMobileByUuid(uuid) موبایل را بگیر
→ کاربر را پیدا یا بساز
→ JWT صادر کن
SMS Providers
دو provider واقعی در Drupal:
KavehNegar:
POST https://api.kavenegar.com/v1/{apiKey}/sms/send.json
form: receptor={mobile}&message={code}&sender=10004346
Rangineh:
از پیادهسازی در sms_provider/src/Plugin/SmsProvider/Rangineh.php الگو بگیر.
Interface در Symfony:
interface SmsProviderInterface {
public function send(string $mobile, string $message): bool;
}
پیکربندی در .env:
SMS_PROVIDER=kavenegar # kavenegar | rangineh | null (dev)
KAVENEGAR_API_KEY=...
KAVENEGAR_SENDER=10004346
Rate Limiting (مقادیر واقعی)
// IP: 50 درخواست در ساعت
// Mobile: 30 درخواست در ساعت
// keys Redis:
// rate_ip:{ip} TTL=3600
// rate_mobile:{mobile} TTL=3600
TTL کد OTP: 1200 ثانیه (20 دقیقه)
در طراحی اولیه اشتباهاً 120 ثانیه نوشته شده بود — مقدار واقعی از Drupal ۱۲۰۰ است.
Flood Control (از Drupal)
علاوه بر rate limiting، Drupal از flood control نیز استفاده میکند:
oauth2_grant.mobile.failed_login_ip— IP basedoauth2_grant.mobile.failed_login_user— User based در Symfony از Symfony'sRateLimitercomponent جایگزین کن.
سازگاری با کلاینت: GET /session/token
return new Response(bin2hex(random_bytes(22)), 200, ['Content-Type' => 'text/plain']);
مجوزها
DELETE /api/v1/user/{id} → ROLE_ADMIN
PATCH /api/v1/user/{id} → owner یا ROLE_ADMIN
ساختار واقعی GET /oauth/userinfo (از سرور Drupal)
{
"email": null,
"email_verified": true,
"username": "09120671710",
"id": "22",
"uuid": "d200f5c5-d717-4526-b263-d3bb7d0228d6",
"created": "1762262151",
"changed": "1762262267",
"status": "1",
"roles": {
"0": "authenticated",
"2": "doctor"
},
"realName": "single doctor",
"picture": [],
"clinic_pro": {
"base_role": "doctor",
"db_uuid": "61be915b-595a-42e5-bca5-f80d22f4f14a",
"db_key": "22bea8c1dc64d9b0c744810722519efe7290276ecd82d9bc650482aa4539bf0d",
"my_doctors_uuid": {
"uuid": "61be915b-595a-42e5-bca5-f80d22f4f14a",
"id": "29",
"name": "single doctor"
}
}
}
نکات مهم userinfo:
rolesیک object (نه array) با کلیدهای عددی است:{"0": "authenticated", "2": "doctor"}idوuuidاز users tablerealNameبا R بزرگ (camelCase)clinic_pro.base_role= نقش اصلی کاربرclinic_pro.db_uuid= UUID موجودیت مرتبط (doctor/clinic/representation)clinic_pro.db_key= token دسترسی برای عملیات داخلیclinic_pro.my_doctors_uuid(فقط برای doctor) = مشخصات پروفایل دکتر
نقشهای مجاز برای login با password
فقط این نقشها میتوانند با POST /oauth/token (grant_type=password) لاگین کنند:
- doctor
- clinic
- doctor_s_secretary
بقیه (patient, representation, admin) فقط از طریق OTP لاگین میکنند.