207 lines
12 KiB
Markdown
207 lines
12 KiB
Markdown
# اتصال gold-bank به سامانه هویت متمرکز (Central IAM)
|
||
|
||
> **نحوه ارتباط (مسیر داده، اندپوینتها، رفتار در خطا):** [`iam-connection.md`](iam-connection.md)
|
||
> **مرجع تصویری API سامانه هویت:** `<آدرس IAM>/docs`
|
||
|
||
این سند توضیح میدهد ورود ادمین چطور میتواند بهجای رمز محلی، از سامانه هویت
|
||
سازمانی اعتبارسنجی شود — **بدون هیچ تغییری در رابط کاربری**.
|
||
|
||
---
|
||
|
||
## ۱. خلاصه تصمیم
|
||
|
||
| موضوع | تصمیم |
|
||
| --- | --- |
|
||
| رابط کاربری | **هیچ تغییری نمیکند**؛ نه دکمه SSO، نه ریدایرکت، نه صفحه جدید |
|
||
| جای اتصال | فقط همان یک نقطهای که رمز بررسی میشد (`AuthService.login`) |
|
||
| پیشفرض | خاموش (`IAM_ENABLED=false`) — رفتار سامانه دقیقاً مثل امروز |
|
||
| در دسترس نبودن IAM | بازگشت خودکار به بررسی رمز محلی ⇒ ورود ادمینها قطع نمیشود |
|
||
| تغییر دیتابیس | دو جدول **جدید** (`AdminSession`، `AppSession`) برای ابطال نشست؛ هیچ جدول موجودی تغییر نکرده |
|
||
| ورود مشتری (OTP پیامکی) | کد همچنان اینجا تولید و بررسی میشود؛ نتیجه به IAM اعلام میگردد تا نشست و خروج سراسری مرکزی شود |
|
||
|
||
جریان ورود ادمین پس از فعال شدن:
|
||
|
||
```
|
||
پنل ادمین ──POST /api/admin/auth/login──▶ API (NestJS)
|
||
(همان فرم و همان بدنه) │
|
||
├─ IAM روشن؟ ──خیر──▶ bcrypt محلی (رفتار فعلی)
|
||
│
|
||
└─بله─▶ POST {IAM}/api/v1/auth/login
|
||
│
|
||
┌─────────────────┴─────────────────┐
|
||
تأیید شد رد/خطا/در دسترس نبود
|
||
│ │
|
||
صدور همان JWT محلی fallback ⇒ bcrypt محلی
|
||
exclusive ⇒ رد ورود
|
||
```
|
||
|
||
پاسخ اندپوینت در هر دو حالت **دقیقاً یکسان** است (`api_token`, `roles`, …)،
|
||
بنابراین پنل ادمین هیچ تفاوتی نمیبیند.
|
||
|
||
---
|
||
|
||
## ۲. فایلها
|
||
|
||
| مسیر | نقش |
|
||
| --- | --- |
|
||
| `apps/api/src/modules/iam/iam.service.ts` | ارتباط با IAM و نگاشت خطاها |
|
||
| `apps/api/src/modules/iam/iam-logout.service.ts` | تأیید توکن خروج سراسری با JWKS (بدون وابستگی جدید) |
|
||
| `apps/api/src/modules/iam/iam.controller.ts` | دریافت اعلان خروج سراسری |
|
||
| `apps/api/src/modules/iam/iam.module.ts` | ماژول Global (تا زمان روشن شدن، بیاثر) |
|
||
| `apps/api/src/modules/sessions/admin-session.service.ts` | نشستهای قابل ابطال (jti) |
|
||
| `apps/api/src/modules/sessions/admin-session.module.ts` | ماژول Global نشستها |
|
||
| `prisma/migrations/20260925120000_admin_session/` | جدول `AdminSession` |
|
||
| `apps/api/src/modules/**/*.spec.ts` | تست هر چهار سرویس بالا |
|
||
|
||
تغییر روی کد موجود، حداقلی است:
|
||
|
||
- `auth.service.ts` — یک `bcrypt.compare` به متد `verifyPassword` منتقل شد و توکن
|
||
صادرشده (در صورت روشن بودن ردیابی نشست) ثبت میشود
|
||
- `auth.controller.ts` — افزودن `@Ip()` و `user-agent`
|
||
- `jwt.strategy.ts` — یک بررسی «نشست باطل نشده باشد» (در حالت خاموش، بدون کوئری)
|
||
- `admin-users.service.ts` — غیرفعال کردن حساب یا تغییر رمز، نشستها را میبندد
|
||
- `app.module.ts` — ثبت `IamModule` و `AdminSessionModule`
|
||
- `prisma/schema.prisma` — فقط مدل جدید `AdminSession` (+ رابطه روی `AdminUser`)
|
||
|
||
---
|
||
|
||
## ۳. فعالسازی
|
||
|
||
### گام ۱ — ثبت این سامانه بهعنوان کلاینت در IAM
|
||
|
||
باید کلاینت از نوع `confidential` و **`is_trusted: true`** باشد؛ فقط چنین
|
||
کلاینتی اجازه «ورود واگذارشده» دارد.
|
||
|
||
```bash
|
||
curl -X POST https://auth.example.com/api/v1/admin/clients \
|
||
-H "Authorization: Bearer $IAM_ADMIN_TOKEN" \
|
||
-H 'content-type: application/json' \
|
||
-d '{
|
||
"client_id": "goldbank_api",
|
||
"client_name": "سامانه بانک طلا",
|
||
"client_type": "confidential",
|
||
"redirect_uris": ["https://goldbank.example.com/unused"],
|
||
"allowed_scopes": ["openid", "profile", "email"],
|
||
"is_trusted": true
|
||
}'
|
||
```
|
||
|
||
> `redirect_uris` در این حالت استفاده نمیشود (جریان مرورگری نداریم) ولی
|
||
> IAM آن را اجباری میداند؛ یک آدرس معتبر بگذارید.
|
||
|
||
### گام ۲ — تنظیم `.env`
|
||
|
||
```dotenv
|
||
IAM_ENABLED=true
|
||
IAM_ISSUER=https://auth.example.com
|
||
IAM_CLIENT_ID=goldbank_api
|
||
IAM_CLIENT_SECRET=<راز دریافتی>
|
||
IAM_TENANT_SLUG=montzar
|
||
IAM_LOGIN_DELEGATION=fallback
|
||
```
|
||
|
||
همین. سرویس را ریاستارت کنید؛ هیچ مایگریشنی لازم نیست.
|
||
|
||
### گام ۳ — بازگشت به عقب
|
||
|
||
`IAM_ENABLED=false` و ریاستارت. تمام.
|
||
|
||
---
|
||
|
||
## ۴. حالتهای `IAM_LOGIN_DELEGATION`
|
||
|
||
| مقدار | رفتار |
|
||
| --- | --- |
|
||
| `off` | IAM اصلاً در ورود دخالت نمیکند |
|
||
| `fallback` (پیشفرض) | اول IAM؛ اگر کاربر آنجا نبود یا سرویس در دسترس نبود، رمز محلی بررسی میشود — مناسب مهاجرت تدریجی |
|
||
| `exclusive` | فقط IAM؛ رمز محلی پذیرفته نمیشود و خرابی IAM یعنی توقف ورود |
|
||
|
||
**پیشنهاد مهاجرت:** با `fallback` شروع کنید، بعد از انتقال همه ادمینها به IAM
|
||
و اطمینان از پایداری، به `exclusive` بروید.
|
||
|
||
---
|
||
|
||
## ۵. نکات عملیاتی
|
||
|
||
### محدودیت نرخ
|
||
|
||
IAM روی «IP» و «هویت» محدودیت نرخ دارد. چون درخواستها از سرور API میآیند،
|
||
IP کاربر نهایی با فیلد `client_ip` فرستاده میشود؛ در غیر این صورت پس از چند
|
||
ورود، کل پنل ادمین ۴۲۹ میگرفت.
|
||
|
||
اگر API پشت پراکسی است، مطمئن شوید Nest `trust proxy` دارد وگرنه `@Ip()`
|
||
همیشه IP پراکسی را میدهد و همان مشکل برمیگردد.
|
||
|
||
### احراز دو مرحلهای
|
||
|
||
فرم ورود این پنل فقط «موبایل + رمز» است و **هیچ مرحلهی ورود کدی ندارد**؛ پس جایی
|
||
برای گرفتن کد عامل دوم وجود ندارد و این مسیر نمیتواند آن را تکمیل کند. ورود چنین
|
||
حسابی با پیام روشن رد میشود (نه خطای مرموز). سه گزینه:
|
||
|
||
1. برای حسابهایی که از این پنل وارد میشوند 2FA را در IAM فعال نکنید (سادهترین)؛
|
||
2. مرحلهی «ورود کد» را به همین فرم اضافه کنید؛ آنوقت میتوان مثل سامانه «همکار»
|
||
کد را واسطه کرد یا با کانال خودِ سامانه فرستاد
|
||
(`/api/v1/auth/mfa/verify` و `/api/v1/auth/mfa/delegate` در IAM آمادهاند)؛
|
||
3. یا جریان مرورگری SSO را برای این پنل به کار بگیرید که خودش صفحه کد را میدهد.
|
||
|
||
> در سامانه «همکار» این محدودیت وجود ندارد، چون ورود کاربر عادی آنجا از قبل مرحلهی
|
||
> «ورود کد» (پیامک) دارد و همان مرحله برای عامل دوم استفاده میشود.
|
||
|
||
### ابطال نشست
|
||
|
||
توکن ادمین خودبسنده و ۱۲ ساعته است، پس تا پیش از این **هیچچیز** نمیتوانست نشست را
|
||
زودتر تمام کند: نه غیرفعال کردن حساب، نه خروج سراسری در IAM. حالا هر توکن صادرشده
|
||
یک `jti` دارد که در جدول `AdminSession` ثبت میشود و درخواست بعدیِ نشستِ باطلشده
|
||
۴۰۱ میگیرد.
|
||
|
||
```
|
||
IAM: ابطال نشست / خروج سراسری
|
||
│ logout_token (RS256، امضاشده)
|
||
▼
|
||
POST /api/iam/backchannel-logout ← این آدرس را در IAM بهعنوان
|
||
│ backchannel_logout_uri کلاینت ثبت کنید
|
||
├─ تأیید امضا با JWKS، بررسی iss/aud/events و نبودن nonce
|
||
└─ ابطال نشستهای متناظر (sid، یا همه نشستهای sub)
|
||
│
|
||
▼
|
||
درخواست بعدی با آن توکن ⇒ ۴۰۱
|
||
```
|
||
|
||
کلید تنظیم: `AUTH_SESSION_TRACKING`
|
||
|
||
| مقدار | رفتار |
|
||
| --- | --- |
|
||
| خالی (پیشفرض) | همراه `IAM_ENABLED` روشن/خاموش میشود — تا ابطال مرکزی بیاثر نماند |
|
||
| `true` | روشن، حتی بدون IAM (ابطال محلی: غیرفعال کردن حساب، تغییر رمز) |
|
||
| `false` | خاموش؛ دقیقاً رفتار قبلی (بدون `jti`، بدون کوئری اضافه) و ابطال مرکزی بیاثر |
|
||
|
||
نکتههای عملی:
|
||
|
||
- توکنهایی که **پیش از** روشن کردن این قابلیت صادر شدهاند `jti` ندارند و تا انقضا
|
||
معتبر میمانند؛ یعنی روشن کردن این کلید کسی را یکباره بیرون نمیاندازد.
|
||
- ردیف نشستِ گمشده «باطل» تلقی میشود، پس پاک کردن جدول یعنی خروج همه.
|
||
- اندپوینت خروج سراسری عمداً بدون احراز هویت است (اعتبارش از امضای توکن میآید) و
|
||
همیشه ۲۰۰ برمیگرداند تا IdP بینهایت تلاش مجدد نکند؛ دلیل رد شدن در لاگ میآید.
|
||
- تنها تغییر شِما یک جدول **جدید** است؛ هیچ جدول موجودی ستون نگرفته. مایگریشن:
|
||
`pnpm --filter @gold-bank/api exec prisma migrate deploy`.
|
||
|
||
### لاگ امنیتی
|
||
|
||
هر ورود واگذارشده در IAM با IP واقعی کاربر و `client_id` این سامانه ثبت
|
||
میشود، پس گزارشهای امنیتی متمرکز کامل باقی میمانند.
|
||
|
||
---
|
||
|
||
## ۶. تست
|
||
|
||
```bash
|
||
cd apps/api
|
||
pnpm test # ۱۱۵ تست، بدون دیتابیس و بدون شبکه
|
||
pnpm test -- iam-logout.service # تأیید توکن خروج سراسری با کلید RSA واقعی
|
||
pnpm test -- admin-session # نشستهای قابل ابطال
|
||
```
|
||
|
||
تستها بدون دیتابیس و بدون شبکه اجرا میشوند و مهمترین چیزی که تضمین میکنند
|
||
این است: **با خاموش بودن قابلیت، هیچ درخواستی به IAM نمیرود و ورود دقیقاً مثل
|
||
قبل کار میکند**؛ و با روشن بودن، خرابی IAM ورود ادمین را قطع نمیکند.
|