# نکات پیاده‌سازی — تسک ۰۲: ماژول احراز هویت

## مهم‌ترین تفاوت با طراحی اولیه

### جریان OTP با UUID (نه موبایل)
در Drupal، کد OTP **با UUID** ذخیره می‌شود، نه با شماره موبایل:
```php
// ساختار ذخیره‌سازی در KeyValue/Redis:
key   = uuid (تولیدشده در send-code)
value = { "code": "12345", "mobile": "09120671713" }
TTL   = 1200 ثانیه
```

`verify-code` و `oauth/token` هر دو `uuid` می‌خواهند، نه موبایل.

```php
// 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:**
```php
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:**
```php
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 (مقادیر واقعی)
```php
// 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 based
- `oauth2_grant.mobile.failed_login_user` — User based
در Symfony از Symfony's `RateLimiter` component جایگزین کن.

## سازگاری با کلاینت: GET /session/token
```php
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)

```json
{
  "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 table
- `realName` با 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 لاگین می‌کنند.
