# راهنمای گرفتن توکن و ورود کلاینت به کلاس‌های plugNmeet این راهنما خلاصه می‌کند که برای سناریوی استاد/دانشجو چگونه از سرویس plugNmeet توکن بگیریم و کلاینت فرانت‌اند (`client/`) با آن وارد کلاس شود. ## پیش‌نیازها - آدرس سرویس: `window.PLUG_N_MEET_SERVER_URL = "https://meet.newhorizonco.uk"` (در `config.js`). - `api_key` و `secret` از فایل پیکربندی بک‌اند (`services/plugnmeet-server/config.yaml`). - بدنهٔ درخواست‌ها باید با پروتکل JSON متناظر با پیام‌های پروتوباف (`plugnmeet-protocol`) ارسال شود؛ سرور طبق `HandleAuthHeaderCheck` هدرهای امنیتی را بررسی می‌کند. ## گام ۱: ایجاد یا فعال بودن اتاق ### API Endpoint برای Django Backend: ``` POST /api/courses//online/room/create/ ``` ### بدنه درخواست از فرانت به Django: ```json { "subject": "کلاس جبر فصل ۱" // اختیاری - عنوان روم } ``` **⚠️ نکات مهم:** - **فرانت نباید `metadata` ارسال کند!** - بک‌اند Django (در `apps/course/views/live_session.py`) به‌طور خودکار تنظیمات امنیتی را اعمال می‌کند - این تضمین می‌کند که تنظیمات امنیتی به‌صورت متمرکز و یکسان اعمال شود ### بدنه درخواست از Django به PlugNMeet (خودکار): بک‌اند Django این بدنه را خودش به PlugNMeet ارسال می‌کند: ```json { "room_id": "algebra-1402", "metadata": { "room_title": "کلاس جبر فصل ۱", "default_lock_settings": { "lock_microphone": true, // 🔒 قفل - فقط میزبان می‌تواند باز کند "lock_webcam": true, // 🔒 قفل - فقط میزبان می‌تواند باز کند "lock_screen_sharing": true // 🔒 قفل - فقط میزبان می‌تواند باز کند }, "room_features": { "mute_on_start": true, // 🔇 همه با میک خاموش وارد می‌شوند "waiting_room_features": { "is_active": false } } } } ``` > **چرا بک‌اند این کار را می‌کند؟** > - ✅ **امنیت متمرکز**: تنظیمات امنیتی در یک جا کنترل می‌شود > - ✅ **جلوگیری از دستکاری**: فرانت نمی‌تواند تنظیمات را تغییر دهد > - ✅ **یکپارچگی**: همه کلاس‌ها با تنظیمات یکسان ساخته می‌شوند > - 🔒 طبق تابع `AssignLockSettingsToUser` در `pkg/models/user_lock.go` این مقادیر برای کاربران غیر-admin اعمال می‌شود ## گام ۲: گرفتن توکن ورود ### API Endpoint برای Django Backend: ``` POST /api/courses/online/room/token/ ``` ### درخواست از فرانت به Django: ``` Headers: Authorization: Token Content-Type: application/json Body: { "course_slug": "algebra-10" } ``` **⚠️ نکات مهم:** - **فرانت فقط `course_slug` ارسال می‌کند!** - بک‌اند Django از `Authorization` header کاربر را شناسایی می‌کند - بک‌اند خودش live session فعال دوره را پیدا می‌کند: ```python # 1. پیدا کردن دوره course = Course.objects.get(slug=course_slug) # 2. پیدا کردن live session فعال session = CourseLiveSession.objects.get( course=course, ended_at__isnull=True # session هایی که هنوز به پایان نرسیده‌اند ) # 3. گرفتن room_id room_id = session.room_id ``` - بک‌اند خودش همه اطلاعات کاربر را می‌سازد: - `user_id` از `request.user` - `name` از `user.get_full_name()` یا `user.email` - `is_admin` از `user.can_manage_course(course)` - `profilePic` از `user.avatar` - `lock_settings` برای غیر-admin ### بدنه درخواست از Django به PlugNMeet (خودکار): بک‌اند Django این payload را خودش می‌سازد و به PlugNMeet می‌فرستد: **برای استاد:** ```json { "room_id": "algebra-1402", "user_info": { "user_id": "10", // 🔐 از request.user "name": "استاد نمونه", // 🔐 از user.get_full_name() "is_admin": true, // 🔐 از user.can_manage_course() "user_metadata": { "is_hidden": false, "profilePic": "https://..." // 🔐 از user.avatar } } } ``` **برای دانشجو:** ```json { "room_id": "algebra-1402", "user_info": { "user_id": "27", // 🔐 از request.user "name": "دانشجو نمونه", // 🔐 از user.get_full_name() "is_admin": false, // 🔐 از user.can_manage_course() "user_metadata": { "profilePic": "https://...", // 🔐 از user.avatar "lock_settings": { // 🔒 خودکار برای غیر-admin "lock_microphone": true, "lock_screen_sharing": true, "lock_webcam": true } } } } ``` ### نحوه کار بک‌اند Django: ```python # 1. شناسایی کاربر از token user = request.user # از Authorization header # 2. پیدا کردن دوره و session فعال course = Course.objects.get(slug=course_slug) session = CourseLiveSession.objects.get(course=course, ended_at__isnull=True) room_id = session.room_id # 3. تشخیص نقش is_admin = user.can_manage_course(course) # استاد یا مالک دوره # 4. ساخت user_info user_info = { 'user_id': str(user.id), 'name': user.get_full_name() or user.email, 'is_admin': is_admin, } # 4. اضافه کردن profilePic profile_pic = request.build_absolute_uri(user.avatar.url) user_metadata['profilePic'] = profile_pic # 5. اضافه کردن lock_settings برای غیر-admin if not is_admin: user_metadata['lock_settings'] = { 'lock_microphone': True, 'lock_screen_sharing': True, 'lock_webcam': True, } ``` ### ارسال به PlugNMeet: بک‌اند Django با هدرهای امنیتی به PlugNMeet ارسال می‌کند: - `API-KEY`: از settings - `HASH-SIGNATURE`: `HMAC_SHA256(body, secret)` - این توکن JWT اختصاصی plugNmeet است که در `GeneratePNMJoinToken` ساخته می‌شود - `is_admin: true` باعث می‌شود در `GetPNMJoinToken` کاربر به عنوان presenter با تمام دسترسی‌ها ثبت شود - `lock_settings` باعث می‌شود در فرانت‌اند PlugNMeet دکمه‌های میکروفون/وبکم غیرفعال شوند ### پاسخ Django به فرانت: ```json { "room_id": "algebra-1402", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "plugnmeet": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires": 300, ... } } ``` فرانت با این `token` می‌تواند کاربر را به PlugNMeet وارد کند: ``` https://meet.newhorizonco.uk/?access_token= ``` ## گام ۳: ورود کلاینت با توکن ۱. توکن را در URL یا کوکی قرار دهید؛ کلاینت مقدار را از `access_token` در کوئری‌استرینگ یا از کوکی `pnm_access_token` می‌خواند (`getAccessToken` در `client/src/helpers/utils.ts`). ۲. آدرس ورود: `https://meet.newhorizonco.uk/?access_token=`. ۳. اپلیکیشن React موجود در `client/src/components/app/index.tsx` پس از بارگذاری: - درخواست `POST /api/verifyToken` را با هدر `Authorization: ` می‌فرستد (`HandleVerifyToken`). - اگر توکن معتبر باشد، لیست آدرس‌های NATS و موضوعات لازم را می‌گیرد و اتصال را آغاز می‌کند (`startNatsConn`). ۴. پس از اتصال، وضعیت کاربر و اتاق در Redux ذخیره می‌شود (`sessionSlice`). اگر کاربر ادمین باشد، تمام امکانات بدون محدودیت فعال است؛ در غیر این صورت مقدارهای `lock_settings` تعیین می‌کنند چه دکمه‌هایی فعال باشند. ## کنترل حالت صحبت/شنیدن برای استاد و دانشجو ### استاد (Moderator/Host): - ✅ در توکن `is_admin: true` ارسال می‌شود - ✅ بک‌اند Django در `apps/course/views/live_session.py` این را تشخیص می‌دهد: ```python is_admin = user.can_manage_course(course) # استاد یا مالک دوره ``` - ✅ سرور PlugNMeet در `GetPNMJoinToken` رول presenter را فعال می‌کند - ✅ **هیچ قفلی** روی میکروفون، وبکم یا اشتراک صفحه اعمال نمی‌شود - 🎤 استاد می‌تواند بلافاصله صحبت کند و به دانشجو **اجازه صحبت** دهد ### دانشجو (Participant): - 🔒 در توکن `is_admin: false` ارسال می‌شود - 🔒 بک‌اند Django خودکار lock_settings را اضافه می‌کند: ```python if not is_admin: user_metadata['lock_settings'] = { 'lock_microphone': True, 'lock_screen_sharing': True, 'lock_webcam': True, } ``` - 🔇 دکمه‌های میکروفون، وبکم و اشتراک صفحه **غیرفعال** هستند - 👂 فقط می‌تواند **گوش دهد** تا میزبان اجازه دهد - این منطق در `joinModal.tsx` با متغیر `isMicLock` پیاده‌سازی شده است ### نحوه دادن اجازه به دانشجو: - میزبان باید از داخل کلاس از طریق UI کنترل کند - یا از API `/api/updateLockSettings` یا `switchPresenter` استفاده کند ## نکات تکمیلی ### توکن‌ها و انقضا: - توکن‌ها زمان انقضای مفهومی دارند (`client.token_validity` در YAML) - در صورت نزدیک شدن به انقضا، کلاینت خودکار با `REQ_RENEW_PNM_TOKEN` درخواست تمدید می‌دهد ### Authorization: - برای درخواست‌های بعدی به `/api/...` همان هدر `Authorization` را ست کنید - کلاینت این کار را در `helpers/api/plugNmeetAPI.ts` انجام می‌دهد ### مدیریت دسترسی‌ها: - اگر می‌خواهید دانشجو را به صحبت‌کننده ارتقا دهید: `/api/updateLockSettings` یا `switchPresenter` - این کار فقط توسط **میزبان** امکان‌پذیر است ## 🔐 جمع‌بندی امنیت ### ❌ چیزهایی که فرانت نباید انجام دهد: #### موقع ساخت روم: - ❌ ارسال `metadata` - ❌ ارسال `default_lock_settings` - ❌ ارسال `room_features` #### موقع گرفتن توکن: - ❌ ارسال `room_id` (بک‌اند خودش از session فعال می‌گیرد) - ❌ ارسال `user_info` - ❌ ارسال `is_admin` - ❌ ارسال `lock_settings` - ❌ ارسال `user_id` یا `name` ### ✅ چیزهایی که فرانت فقط ارسال می‌کند: #### موقع ساخت روم: ```json { "room_id": "algebra-1402", // اختیاری "subject": "کلاس جبر" // اختیاری } ``` #### موقع گرفتن توکن: ```json { "course_slug": "algebra-10" // فقط این! } ``` + `Authorization: Token ` در header ### ✅ چیزهایی که بک‌اند Django خودش انجام می‌دهد: #### برای همه درخواست‌ها: - ✅ شناسایی کاربر از `Authorization` header - ✅ بررسی دسترسی با `user.can_manage_course()` یا `Participant.objects.filter()` #### موقع ساخت روم: - ✅ تعیین `default_lock_settings` (همه `true`) - ✅ تعیین `room_features.mute_on_start: true` - ✅ ساخت `metadata` کامل برای PlugNMeet #### موقع گرفتن توکن: - ✅ پیدا کردن live session فعال از `course_slug` - ✅ گرفتن `room_id` از session - ✅ ساخت `user_id` از `request.user.id` - ✅ ساخت `name` از `user.get_full_name()` یا `user.email` - ✅ تشخیص `is_admin` از `user.can_manage_course(course)` - ✅ گرفتن `profilePic` از `user.avatar` - ✅ اضافه کردن `lock_settings` برای غیر-admin - ✅ ساخت `user_info` کامل برای PlugNMeet **نتیجه:** - 🔒 **امنیت کامل**: فرانت نمی‌تواند هیچ تنظیمات امنیتی را دستکاری کند - ✅ **متمرکز**: همه logic در بک‌اند Django است - 🎯 **ساده**: فرانت فقط `course_slug` و `Authorization` header ارسال می‌کند - 🔐 **قابل کنترل**: بک‌اند تعیین می‌کند کدام session فعال است