Files
tehran_tala/docs/iam-sso.md
T
2026-10-07 16:45:36 +03:30

14 KiB
Raw Blame History

اتصال gold-bank به سامانه هویت متمرکز (Central IAM)

نحوه ارتباط (مسیر داده، اندپوینت‌ها، رفتار در خطا): iam-connection.md مرجع تصویری API سامانه هویت: <آدرس IAM>/docs

این سند توضیح می‌دهد ورود ادمین چطور می‌تواند به‌جای رمز محلی، از سامانه هویت سازمانی اعتبارسنجی شود — بدون هیچ تغییری در رابط کاربری.


۱. خلاصه تصمیم

موضوع تصمیم
رابط کاربری فرم ورود همان است. با IAM روشن، یک پرش بی‌صدای prompt=none و صفحه /auth/iam/callback اضافه شده تا ورود مشترک با همکار کار کند
جای اتصال فقط همان یک نقطه‌ای که رمز بررسی می‌شد (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 باشد؛ فقط چنین کلاینتی اجازه «ورود واگذارشده» دارد.

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 آدرس callback فرانت است و باید دقیقاً با صفحه‌ای که مرورگر باز می‌کند یکی باشد. برای اپ کاربر و پنل ادمین، و برای دولوپ و پروداکشن، هر کدام یک خط جدا. جزئیات در بخش «ورود مشترک با همکار».

گام ۲ — تنظیم .env

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 این سامانه ثبت می‌شود، پس گزارش‌های امنیتی متمرکز کامل باقی می‌مانند.


۶. ورود مشترک با همکار

کوکی نشست روی خود IAM است (iam.montest.ir) و بین دامنه‌ها مشترک نمی‌شود. ورود در یک سامانه فقط وقتی سامانهٔ دیگر را هم باز می‌کند که:

  1. ورود محلی بلیت مرورگر گرفته باشد و فرانت به iamBootstrapUrl رفته باشد.
  2. سامانهٔ دوم بدون توکن محلی، prompt=none بزند و callback همان صفحه باشد.
  3. همان شماره موبایل در سامانهٔ دوم از قبل حساب داشته باشد. IAM حساب gold-bank یا همکار را نمی‌سازد.

کلاینت gold-bank جدا از hamkar_server است (goldbank_api). آدرس‌ها عیناً، هر کدام یک خط در فرم IAM و با کاما در env:

نقش callback (آدرس‌های بازگشت) بعد از خروج
اپ کاربر IAM_REDIRECT_URI و IAM_REDIRECT_URIS IAM_POST_LOGOUT_REDIRECT_URI و IAM_ALLOWED_RETURN_URLS
پنل ادمین IAM_ADMIN_REDIRECT_URI و IAM_ADMIN_REDIRECT_URIS IAM_ADMIN_POST_LOGOUT_REDIRECT_URI و IAM_ADMIN_ALLOWED_RETURN_URLS

فرانت آدرس خودش را می‌فرستد (origin + /auth/iam/callback؛ پنل شعبه پیشوند /branch دارد). اگر آن رشته در فهرست نباشد، بررسی بی‌صدا انجام نمی‌شود و صفحهٔ لاگین محلی می‌ماند. خروج هم اگر مقصدش ثبت نشده باشد به IAM نمی‌رود.

وقتی IAM_ENABLED خاموش است، sso/start مقدار enabled: false می‌دهد و sso/logout-url مقدار logout_url: null. هیچ پیام خطایی برای خروج لازم نیست.


۷. تست

cd apps/api
pnpm test                       # ۱۱۵ تست، بدون دیتابیس و بدون شبکه
pnpm test -- iam-logout.service # تأیید توکن خروج سراسری با کلید RSA واقعی
pnpm test -- admin-session      # نشست‌های قابل ابطال

تست‌ها بدون دیتابیس و بدون شبکه اجرا می‌شوند و مهم‌ترین چیزی که تضمین می‌کنند این است: با خاموش بودن قابلیت، هیچ درخواستی به IAM نمی‌رود و ورود دقیقاً مثل قبل کار می‌کند؛ و با روشن بودن، خرابی IAM ورود ادمین را قطع نمی‌کند.