Files
tehran_taj/.cursor/rules/40-iam-sso.mdc
2026-10-07 16:45:36 +03:30

87 lines
6.0 KiB
Plaintext

---
description: اتصال ورود ادمین به سامانه هویت متمرکز (IAM) — مرزها و قواعدی که نباید شکسته شوند
globs:
alwaysApply: true
---
# اتصال به سامانه هویت متمرکز (Central IAM)
مستندها:
- `docs/iam-connection.md` — **نحوه ارتباط**: سه کانال داده و رفتار در خطا
- `docs/iam-sso.md` — پیکربندی، ورود مشترک با همکار، ابطال نشست
- مرجع تصویری اندپوینت‌های IAM: `<IAM>/docs` (Scalar)
## اصل غیرقابل‌مذاکره
این قابلیت **افزایشی** است. با `IAM_ENABLED=false` (پیش‌فرض) سامانه باید دقیقاً
مثل قبل رفتار کند: یک `bcrypt.compare` محلی و هیچ درخواست شبکه‌ای.
- فرم ورود عوض نمی‌شود. پاسخ ورود همان `api_token` قبلی است، به‌اضافهٔ
`iamBootstrapUrl` فقط وقتی IAM بلیت مرورگر داده باشد.
- وقتی IAM روشن است و `IAM_ADMIN_REDIRECT_URI` (یا یکی از
`IAM_ADMIN_REDIRECT_URIS`) با آدرس خودِ پنل یکی است، بار اول بدون توکن یک
پرش `prompt=none` به `/auth/iam/callback` می‌رود. اپ کاربر همین کار را با
`IAM_REDIRECT_URI` و `IAM_REDIRECT_URIS` می‌کند. آدرس ناشناس جایگزین آدرس
ثبت‌شده نمی‌شود و پاسخ `enabled: false` می‌ماند.
- IAM خاموش، یا redirect ادمین خالی، یعنی این پرش شروع نمی‌شود و خروج آدرس
مرکزی برنمی‌گرداند.
## نقشه فایل‌ها
| مسیر | نقش |
| --- | --- |
| `apps/api/src/modules/iam/iam.service.ts` | ارتباط با IAM و نگاشت خطاها |
| `apps/api/src/modules/iam/iam.module.ts` | ماژول Global (بی‌اثر تا زمان روشن شدن) |
| `apps/api/src/modules/auth/auth.service.ts` | متد `verifyPassword` — تنها نقطه اتصال ورود |
| `apps/api/src/modules/sessions/admin-session.service.ts` | نشست‌های قابل ابطال (`jti`) |
| `apps/api/src/modules/iam/iam-logout.service.ts` | تأیید توکن خروج سراسری (JWKS، RS256) |
| `apps/api/src/modules/iam/iam.controller.ts` | `POST /api/iam/backchannel-logout` |
## قواعد سخت
1. **هر نتیجه مبهم باید به `bcrypt` محلی برگردد.** سرویس در دسترس نیست، کاربر
آنجا نیست، یا ۴۲۹ گرفتیم ⇒ رمز محلی بررسی شود. فقط `exclusive` اجازه رد
کردن دارد. خرابی IAM هرگز نباید ادمین‌ها را بیرون بیندازد.
2. **حساب قفل‌شده، «نتیجه قطعی» است نه خطا.** `account_locked` نباید به
`bcrypt` محلی برگردد وگرنه قفلِ IAM بی‌اثر می‌شود.
3. **`client_ip` همیشه فرستاده می‌شود.** بدون آن، محدودیت نرخِ IAM روی IP همین
سرور اعمال می‌شود و بعد از چند ورود کل پنل ادمین ۴۲۹ می‌گیرد. اگر API پشت
پراکسی رفت، `trust proxy` را تنظیم کن وگرنه `@Ip()` همیشه IP پراکسی است.
4. **هیچ جدول موجودی تغییر نمی‌کند.** نگاشت کاربر با همان `mobile` انجام می‌شود و
`iamSub`/`iamSessionId` روی جدول **جدید** `AdminSession` می‌نشینند، نه روی
`AdminUser`. اگر روزی چیزی از IAM لازم شد، همین الگو را نگه دار.
5. **ابطال نشست دو شرط دارد و هر دو لازم است:** توکن باید `jti` داشته باشد و ردیف
`AdminSession` باید موجود و باطل‌نشده باشد. با خاموش بودن ردیابی
(`AUTH_SESSION_TRACKING=false` یا IAM خاموش) نه `jti` صادر می‌شود، نه کوئری
اضافه‌ای در مسیر احراز هویت اجرا می‌شود — یعنی دقیقاً رفتار و هزینه قبلی.
توکن بدون `jti` هرگز نباید رد شود، وگرنه روشن کردن این کلید همه را بیرون می‌اندازد.
6. **اندپوینت خروج سراسری بدون احراز هویت است و باید بماند.** اعتبارش از امضای
`logout_token` می‌آید: `iss`، `aud`، `events`، نبودن `nonce` و `exp` همه بررسی
می‌شوند. هیچ‌یک از این بررسی‌ها را برای «راحتی تست» حذف نکن. پاسخ همیشه ۲۰۰ است
تا IdP بی‌نهایت تلاش مجدد نکند؛ دلیل رد شدن فقط لاگ می‌شود.
7. **فرم ورود این پنل مرحله «ورود کد» ندارد**، پس حساب دو مرحله‌ای در IAM از این
مسیر قابل تکمیل نیست و با پیام روشن رد می‌شود. تا وقتی آن مرحله ساخته نشده،
تلاش برای تکمیل 2FA در بک‌اند بی‌معناست.
8. **ورود اپ کاربر رمز ندارد**، پس از `verifyCredentials` استفاده نمی‌شود؛ بعد از
بررسی موفق کد، `IamService.attestOtpLogin` فراخوانی می‌شود. قاعده خطا: «کاربر
آنجا نیست» و «سرویس در دسترس نیست» ⇒ ورود ادامه می‌یابد؛ «قفل/غیرفعال/دو
مرحله‌ای» ⇒ ورود رد می‌شود. کد اضطراری تحویل (`OTP_MASTER_CODE`) هرگز به IAM
اعلام نمی‌شود.
9. پیکربندی ناقص = خاموش. `IamService.enabled` وجود `issuer`, `client_id` و
`client_secret` را هم شرط می‌کند (با `envOr` که مقدار خالی را unset می‌شمارد).
## قبل از تحویل
```bash
cd apps/api && npx jest src/modules/iam src/modules/auth src/modules/sessions
# و در استقرار: prisma migrate deploy (جدول AdminSession)
```