# کاربران و احراز هویت

دامنهٔ `identity` مالک اطلاعات قانونی و تماس کاربر، سطح احراز هویت و درخواست‌های
تطبیق Provider است. دامنهٔ `profile` فقط اطلاعات نمایشی مانند avatar و ترجیحات
عمومی را نگه می‌دارد و کدملی را در اختیار ندارد.

## اطلاعات کاربر

- نام و نام خانوادگی
- کد تماس کشور با قالب E.164 مانند `+98`
- شماره موبایل ملی و مقدار نرمال‌شدهٔ کامل در `phone`
- کدملی برای کاربران `+98`
- تاریخ ثبت‌نام و زمان تکمیل هر سطح

شماره `09121234567` همراه `+98` به `+989121234567` نرمال می‌شود. در نسخهٔ فعلی
انتخاب ایران از روی کد تماس `+98` تشخیص داده می‌شود. اگر در آینده کشور اقامت یا
تابعیت مستقل از شماره تماس لازم باشد باید فیلد ISO 3166 جداگانه اضافه شود.

## ثبت‌نام و احراز تکمیلی

```mermaid
flowchart LR
  A["اطلاعات و رمز ثبت‌نام"] --> C{"کد تماس +98؟"}
  C -->|"خیر"| L1["ایجاد حساب LEVEL_1"]
  C -->|"بله"| O["ارسال و تأیید OTP ثبت‌نام"]
  O --> N["استعلام شاهکار داخل ثبت‌نام"]
  N -->|"عدم تطبیق"| R["رد ثبت‌نام؛ حساب ساخته نمی‌شود"]
  N -->|"تأیید"| L2["ایجاد حساب LEVEL_2"]
  L2 --> S["درخواست یک عملیات حساس در آینده"]
  S --> B["احراز بایومتریک وابسته به purpose"]
  B --> P["Proof کوتاه‌عمر؛ سطح کاربر تغییر نمی‌کند"]
```

برای شماره‌های ایران ابتدا OTP شماره با قالب ثبت‌نام تأیید و یک
`registrationToken` کوتاه‌عمر صادر می‌شود؛ سپس شاهکار قبل از ایجاد User اجرا
می‌شود. OTP مصرف‌شده قابل استفاده مجدد نیست. تنها نتیجهٔ
`succeeded + matched=true` اجازه ساخت حساب را می‌دهد. عدم تطبیق با خطای قابل
پردازش `IDENTITY_NATIONAL_PHONE_MISMATCH` و قطعی Provider با
`IDENTITY_NATIONAL_PROVIDER_UNAVAILABLE` پاسخ داده می‌شود. برای جلوگیری از
تکرار ناخواسته تماس Provider، `Idempotency-Key` در ثبت‌نام ایرانی اجباری است.

## API فعلی

| عملیات                         | Endpoint                                                      | دسترسی                                   |
| ------------------------------ | ------------------------------------------------------------- | ---------------------------------------- |
| ثبت‌نام کاربر APP              | `POST /api/v1/identity/registrations`                         | عمومی؛ Idempotency-Key برای ایران الزامی |
| درخواست OTP ثبت‌نام            | `POST /api/v1/auth/registration/otp`                          | عمومی؛ فقط شماره ایران                   |
| تأیید OTP ثبت‌نام              | `POST /api/v1/auth/registration/otp/verify`                   | عمومی                                    |
| مشاهده وضعیت                   | `GET /api/v1/identity/users/:userId/verification`             | Bearer + `users:read`                    |
| احراز بایومتریک تکمیلی         | `POST /api/v1/identity/users/:userId/verifications/biometric` | Bearer + Idempotency-Key                 |
| وضعیت احراز کاربر جاری اپ      | `GET /api/v1/identity/me/verification`                        | Bearer                                   |
| ارتقای بایومتریک کاربر جاری اپ | `POST /api/v1/identity/me/verifications/biometric`            | Bearer + Idempotency-Key                 |

ثبت‌نام اکنون فیلد `password` با حداقل هشت نویسه و ترکیب حرف و عدد می‌گیرد.
این ثبت‌نام مخصوص حساب‌های عادی اپلیکیشن با نوع `APP` است و هیچ دسترسی به پنل
ایجاد نمی‌کند. حساب `PANEL` فقط توسط مدیر دارای مجوز `users:manage` ساخته می‌شود.
مسیرهای ورود و بازیابی رمز عبارت‌اند از:

| عملیات               | Endpoint                            |
| -------------------- | ----------------------------------- |
| ورود با موبایل و رمز | `POST /api/v1/auth/login`           |
| ورود اختصاصی پنل     | `POST /api/v1/auth/panel/login`     |
| چرخش Refresh Token   | `POST /api/v1/auth/refresh`         |
| خروج و ابطال Session | `POST /api/v1/auth/logout`          |
| درخواست OTP بازیابی  | `POST /api/v1/auth/password/forgot` |
| تأیید OTP            | `POST /api/v1/auth/password/verify` |
| تعیین رمز جدید       | `POST /api/v1/auth/password/reset`  |

مدیریت کاربران پنل از `GET/POST /api/v1/panel/users` و
`POST /api/v1/panel/users/:userId/access` انجام می‌شود. تغییر دسترسی حساب خود
ممنوع است، اعطای `super_admin` فقط توسط Super Admin انجام می‌شود و همه تغییرات
در Audit Log ثبت می‌شوند.

مدیریت و بررسی حساب‌های عادی اپلیکیشن از مسیرهای جدا انجام می‌شود:

| عملیات                                                 | Endpoint                                | مجوز           |
| ------------------------------------------------------ | --------------------------------------- | -------------- |
| فهرست، جست‌وجو و فیلتر کاربران APP                     | `GET /api/v1/panel/app-users`           | `users:read`   |
| مشاهده اطلاعات هویتی Mask‌شده، سوابق احراز و Sessionها | `GET /api/v1/panel/app-users/:userId`   | `users:read`   |
| ویرایش نام و نام خانوادگی                              | `PATCH /api/v1/panel/app-users/:userId` | `users:manage` |

کدملی در این API هرگز به‌صورت کامل برگردانده نمی‌شود. شماره موبایل، کدملی و سطح
احراز از فرم عمومی پنل قابل تغییر نیستند؛ تغییر آن‌ها باید در آینده از workflow
اختصاصی همراه با تأیید مجدد انجام شود. ویرایش پروفایل در `AuditLog` ثبت می‌شود.

احراز تکمیلی بایومتریک علاوه بر `purpose` عملیات، تاریخ تولد شمسی، سریال کارت،
متن گفتار، ویدئوی Base64 و رضایت صریح کاربر را دریافت می‌کند. تأیید بایومتریک
برای purposeهای عملیاتی، `verificationLevel` کاربر را تغییر نمی‌دهد و یک نتیجه
وابسته به purpose با `validUntil` کوتاه‌عمر ایجاد می‌کند. تنها purpose داخلی
`identity.level-up` در صورت تأیید کامل Provider سطح پایدار کاربر را به `LEVEL_3`
ارتقا می‌دهد. Base64 فقط در حافظه اعتبارسنجی و به Provider ارسال می‌شود و
در Identity، Log، Outbox یا snapshot دیتابیس ذخیره نمی‌شود. حداکثر اندازهٔ فایل
decode شده ۵MiB و body limit سرور ۷.۵MB است. پس از تکمیل Media، ورودی عمومی باید
به `videoMediaId` خصوصی تغییر کند و backend فایل را از Storage بخواند.

## حفاظت از داده

کدملی خام در پاسخ، Log، Outbox یا جدول درخواست Provider ذخیره نمی‌شود. مقدار
اصلی با AES-256-GCM و کلید `IDENTITY_DATA_ENCRYPTION_KEY` رمز می‌شود، HMAC آن
برای جلوگیری از ثبت تکراری و چهار رقم آخر برای نمایش Mask شده نگه‌داری می‌شود.
کلید production باید Base64 و دقیقاً ۳۲ بایت، خارج از Repository و قابل rotation
باشد. Rotation قبل از تعویض کلید به یک job رمزگشایی/رمزگذاری مجدد نیاز دارد.

## اتصال Provider واقعی در مرحله بعد

Providerهای واقعی باید `NationalIdentityVerificationProvider` و
`BiometricVerificationProvider` را implement کنند. Adapter مسئول تبدیل DTO،
timeout، retry، circuit breaker، احراز webhook و نگاشت خطا است. دامنه فقط نتیجهٔ
استاندارد و `trackingId` را دریافت می‌کند. Mockها در production اجرا نمی‌شوند.

برای Providerهای asynchronous، callback باید درخواست را با `(provider,
trackingId)` پیدا کند، امضا و replay window را بررسی کند و سپس همان transition
و Outbox را اجرا کند. جزئیات callback بعد از دریافت قرارداد API Provider اضافه
می‌شود.

### شاهکار API.ir

Adapter ثبت‌نام ایرانی برای `POST /api/sw1/Shahkar` آماده است و شماره E.164 داخلی مانند
`+989121234567` را به قالب Provider یعنی `09121234567` تبدیل می‌کند. فعال‌سازی
محلی:

```env
NATIONAL_IDENTITY_PROVIDER=shahkar
API_IR_BASE_URL=https://s.api.ir
SHAHKAR_TOKEN=replace-with-shahkar-secret-token
API_IR_TIMEOUT_MS=15000
```

Token فقط از Secret محیط خوانده می‌شود. درخواست‌های شبکه/5xx با backoff محدود
retry می‌شوند، پاسخ‌های 4xx retry نمی‌شوند و circuit breaker از فشار مداوم روی
Provider خراب جلوگیری می‌کند.

طبق تعریف فیلدها، فقط وقتی `success=true` باشد مقدار `data` نتیجه تطبیق است.
نمونه مستند Provider به‌شکل متناقض `data=true` و `success=false` دارد؛ Adapter
در این حالت عمداً نتیجه را `FAILED` ثبت می‌کند و هویت کاربر را ارتقا نمی‌دهد.
اگر Provider رسماً معنای متفاوتی اعلام کند Mapper و contract test اصلاح می‌شوند.

عبارت «دیتای رمز‌شده» در توضیح سرویس، الگوریتم یا envelope رمزنگاری جداگانه‌ای
ارائه نکرده و نمونه رسمی body ساده است؛ بنابراین Adapter دقیقاً body مستند را
روی HTTPS ارسال می‌کند. افزودن رمزنگاری اختصاصی بدون مستند کلید/الگوریتم مجاز
نیست.

### احراز ویدئویی API.ir

Adapter `POST /api/sw1/VideoVerify` اطلاعات زیر را از قرارداد داخلی می‌گیرد:

- `birthDate` در API کاپیلا به‌صورت timestamp استاندارد UTC و ISO-8601، مانند
  `1992-03-21T00:00:00.000Z` دریافت می‌شود. Adapter سرویس ایرانی آن را فقط هنگام
  ارسال به Provider به قالب شمسی `1371/1/1` تبدیل می‌کند.
- `serialNumber` با حداقل پنج کاراکتر
- `speechText` با حداقل ده کاراکتر
- `videoBase64` معتبر با حداکثر اندازه decode شده ۵MiB
- کدملی رمزگشایی‌شده فقط در لحظه تماس با Provider

Thresholdها از سمت client پذیرفته نمی‌شوند تا کاربر نتواند سطح امنیت را کاهش
دهد و از تنظیمات سرور خوانده می‌شوند:

```env
BIOMETRIC_PROVIDER=api_ir_video
VIDEO_VERIFY_TOKEN=replace-with-biometric-secret-token
VIDEO_VERIFY_MATCHING_THRESHOLD=80
VIDEO_VERIFY_LIVENESS_THRESHOLD=80
VIDEO_VERIFY_SPEECH_THRESHOLD=50
```

نتیجه پاک‌سازی‌شده شامل score و booleanهای تطبیق چهره، زنده‌سنجی، گفتار و نتیجه
نهایی ذخیره می‌شود. Proof تکمیلی فقط وقتی تأیید می‌شود که `success=true` و هر
چهار خروجی `isMatch`، `isLiveness`، `isSpeechMatched` و `isPassed` مثبت باشند.
این Proof به `purpose` وابسته و به‌صورت پیش‌فرض ۱۵ دقیقه معتبر است. نمونه مستند
این سرویس نیز `success=false` همراه نتیجه مثبت دارد و با همان سیاست سخت‌گیرانه
به `FAILED` تبدیل می‌شود.

متن گفتار باید از متد `VideoSpeechText` تأمین‌کننده گرفته شود. چون قرارداد آن
هنوز ارائه نشده، endpoint تولید متن ساخته نشده و فعلاً `speechText` معتبر در
درخواست احراز تکمیلی دریافت می‌شود.
