مدیریت آموزشگاه

آدیت و تست استرس

آدیت و تست استرس نسخهٔ وب

بازبینی کامل نسخهٔ وب در شهریور ۱۴۰۵؛ امنیت، صحت و عملکرد، همراه با اصلاح‌هایی که به همراه داشت و اعدادی که اندازه گرفت.

خلاصه

نسخهٔ وب در تاریخ ۱۴ شهریور ۱۴۰۵ (۵ سپتامبر ۲۰۲۶) سرتاسر بازبینی شد: بازبینی امنیتی در سطح کد، بازبینی صحت در برابر قوانین اپ موبایل، و تست استرس با یک آموزشگاه ۲٬۰۰۰ هنرجویی و تا ۱۰۰ کاربر هم‌زمان. هیچ نقص یکپارچگی داده یا امنیتی پیدا نشد. بازبینی نُه بهبود به همراه داشت (در ادامه فهرست شده) که همه اعمال شدند، و مجموعه‌ای از حدود اندازه‌گیری‌شده که راهنمای استقرار حالا آن‌ها را منعکس می‌کند.

حوزه نتیجه
تست واحد (دفتر کل، اعتبار، معرفی، بایگانی، پشتیبان، رمزنگاری، تقویم) ۳۷ / ۳۷ پاس
پیمایش سرتاسری HTTP روی همهٔ جریان‌ها ۲۰ / ۲۰ مرحله پاس
بررسی تایپ (svelte-check) ۰ خطا
آدیت وابستگی‌ها (npm audit --omit=dev) ۰ آسیب‌پذیری در وابستگی‌های تولید
تست بار، ۱ پروسه، ۲۵ تا ۱۰۰ کاربر هم‌زمان ۰ خطا، حدود ۴۰ صفحه در ثانیه
تست بار، ۴ پروسه، ۱۰۰ کاربر هم‌زمان ۰ خطا، ۱۲۷ صفحه در ثانیه

دامنه و روش

  • بازبینی کد همهٔ ماژول‌های سرور: احراز هویت، مدیریت نشست، محافظت از مسیرها، اکشن‌های فرم، سرو کردن فایل، ورود/خروج پشتیبان، لایهٔ SQLite و ایمیج Docker.
  • مقایسهٔ قانون به قانون با مشخصات اپ موبایل (فرمول مانده، ثابت طلایی final_amount، مصرف اعتبار جلسات، آبشارهای بایگانی، کلیدهای dedupe اعلان‌ها، نصاب معرفی، پاکت پشتیبان).
  • بررسی‌های خودکار: ۳۷ تست واحد Vitest روی دیتابیس موقت، یک تست دود ۲۰ مرحله‌ای HTTP که همهٔ صفحه‌ها را از همان اکشن‌های فرمی که مرورگر استفاده می‌کند رد می‌کند، svelte-check، npm audit.
  • تست استرس: یک آموزشگاه ۲٬۰۰۰ هنرجویی که از طریق HTTP پر شد، سپس ترکیب وزنی از بارگذاری صفحه‌های واقعی و نوشتن‌ها از ۲۵، ۵۰ و ۱۰۰ کاربر مجازی، هر کدام ۳۰ ثانیه، روی یک لپ‌تاپ که مولد بار هم روی همان اجرا می‌شد (پس همهٔ اعداد پایین محافظه‌کارانه‌اند).

بازبینی امنیتی

بررسی وضعیت
احراز هویت برای هر صفحه، فایل و دانلود؛ اولین اجرا فقط به ساخت حساب محدود است پیاده‌سازی‌شده
هش رمز با scrypt (N = 16384)، salt تصادفی، مقایسهٔ زمان‌ثابت پیاده‌سازی‌شده
توکن نشست به‌صورت هش (SHA-256) ذخیره می‌شود؛ httpOnly، SameSite=Lax، Secure روی HTTPS؛ انقضای لغزان ۳۰ روزه؛ پایان همهٔ نشست‌ها با تغییر رمز پیاده‌سازی‌شده
محدودیت ورود: ۱۰ شکست به ازای هر آدرس در ۱۵ دقیقه پیاده‌سازی‌شده
CSRF: بررسی origin توسط SvelteKit روی هر ارسال فرم (ORIGIN / هدرهای پروکسی) پیاده‌سازی‌شده
سیاست امنیت محتوا (CSP) با nonce برای هر درخواست: اسکریپت فقط از مبدأ برنامه، بدون اسکریپت درون‌خطی، بدون اتصال خارجی در این آدیت اضافه شد
هدرهای امنیتی: nosniff، frame-ancestors/X-Frame-Options، referrer policy، permissions policy، HSTS در تولید پیاده‌سازی‌شده
SQL: همهٔ دستورها پارامتری؛ هیچ ورودی کاربر به متن SQL نمی‌رسد تأییدشده
XSS: Svelte همهٔ درون‌یابی‌ها را escape می‌کند؛ هیچ {@html} وجود ندارد تأییدشده
پیمایش مسیر: نام فایل عکس و پشتیبان اعتبارسنجی و داخل پوشهٔ داده resolve می‌شود با تست تأییدشده
Zip-slip در بازیابی: ورودی‌های با ..، مسیر مطلق یا نام فایل کلید دستگاه رد می‌شوند با تست تأییدشده
اعتبارسنجی بازیابی: بررسی integrity و اسکیما قبل از جایگزینی دیتابیس زنده؛ بازگشت خودکار در شکست با تست تأییدشده
محدودیت آپلود: پیش‌فرض ۶۴ مگابایت (MAX_UPLOAD_MB، BODY_SIZE_LIMIT) پیاده‌سازی‌شده
رمزنگاری پشتیبان: PBKDF2-SHA256 با ۲۱۰ هزار تکرار، AES-256-CBC، تأیید HMAC-SHA256 قبل از رمزگشایی؛ نسخهٔ قدیمی v1 خواندنی با تست تأییدشده
رمز پشتیبان خودکار با کلیدی خارج از دیتابیس و خارج از هر پشتیبان، رمزشده ذخیره می‌شود پیاده‌سازی‌شده
حساب‌ها و نشست‌ها در دیتابیس جدا، هرگز داخل پشتیبان نیستند پیاده‌سازی‌شده
کانتینر با کاربر غیر root، یک Volume، healthcheck پیاده‌سازی‌شده
هیچ رازی لاگ نمی‌شود تأییدشده

یادداشت‌های باقی‌مانده: یک نقش مدیر واحد وجود دارد (بدون مجوز به ازای کاربر)، ورود دومرحله‌ای ندارد، و TLS داخلی ندارد (HTTPS در پروکسی خاتمه می‌یابد؛ Coolify همین کار را می‌کند).

بازبینی صحت

هر قانون اپ موبایل به کد وب ردیابی و با یک تست پوشش داده شد:

  • مانده = Σفاکتور − Σتخفیف − Σمعرفی − Σپرداخت + Σتعدیل − Σبازپرداخت؛ ردیف‌های باطل صفر حساب می‌شوند؛ به تفکیک ارز.
  • enrollment.final_amount همیشه برابر دفتر زندهٔ همان ثبت‌نام است؛ بعد از هر درج، ویرایش، ابطال و بازگردانی بازمحاسبه می‌شود؛ ثبت‌نامی که همهٔ ردیف‌هایش باطل شده به صفر بازمحاسبه می‌شود.
  • تخفیف دستی هرگز از شهریه بیشتر نمی‌شود (فرم و ریپازیتوری).
  • حاضر و تأخیر یک اعتبار جلسه مصرف می‌کنند؛ غایب و موجه نه؛ تغییر وضعیت اعتبار را پس می‌دهد یا می‌گیرد.
  • لغو ثبت‌نام فاکتور، تخفیف و اعتبار معرفی را باطل می‌کند و پرداخت‌ها را نگه می‌دارد؛ پس هنرجو بستانکار می‌شود.
  • آبشار بایگانی فرزندان را علامت می‌زند؛ بازگردانی فقط همان‌ها را برمی‌گرداند؛ بازگردانی کلاس، ترم بایگانی‌شده‌اش را هم برمی‌گرداند.
  • حذف دائمی رکورد بایگانی‌شده می‌خواهد و مانده‌های نهایی را در لاگ حسابرسی می‌نویسد؛ حذف ترم و کلاس ردیف‌های دفتر را باطل و جدا می‌کند.
  • نصاب معرفی اعتبار را به مقدار بدهی محدود می‌کند و وقتی چیزی برای اعتبار دادن نیست مصرف نمی‌شود؛ معرفی خود و چرخشی رد می‌شود.
  • اعلان‌ها با کلید dedupe می‌شوند؛ هشدار کم بودن اعتبار روی تعداد باقی‌مانده کلید می‌خورد.
  • پشتیبان‌ها بایت‌به‌بایت با فرمت موبایل رفت‌وبرگشت می‌کنند؛ رمز غلط و بایت دست‌کاری‌شده رد می‌شوند.

یک تصمیم طراحی بدون تغییر و مستند مانده: نوع ردیف refund مثل پرداخت مانده را کم می‌کند و هیچ صفحه‌ای آن را نمی‌سازد.

بهبودهای انجام‌شده در این آدیت

  1. سیاست امنیت محتوا با nonce (script-src 'self').
  2. صفحه‌بندی فهرست هنرجویان (۶۰ در هر صفحه) با شمارش کامل؛ صفحه‌بندی اعلان‌ها (۵۰ در هر صفحه).
  3. محدود شدن فهرست‌های پیشخوان به آنچه نمایش داده می‌شود؛ بازنویسی فیلتر بدهکار از زیرپرس‌وجوی همبسته به یک گذر گروه‌بندی‌شده روی دفتر.
  4. شمارش هنرجویان فهرست کلاس‌های حضور و غیاب در یک پرس‌وجوی گروه‌بندی‌شده به‌جای یکی برای هر کلاس.
  5. صفحه‌های کلاس، ترم و معرفی فقط نام هنرجویانی را که نمایش می‌دهند بار می‌کنند.
  6. کش یک‌ثانیه‌ای تنظیمات در حافظه (۱۴ پرس‌وجو کمتر برای هر درخواست).
  7. هدر X-Response-Time روی هر پاسخ برای مانیتورینگ.
  8. حالت چندپروسه‌ای اختیاری (WORKERS=n|auto)؛ زمان‌بند فقط در یک worker اجرا می‌شود.
  9. فلگ Secure کوکی از پروتکل درخواست پیروی می‌کند؛ اجرای محلی ORIGIN را خودکار می‌سازد.

تست استرس

مجموعهٔ داده

جدول تعداد ردیف
هنرجویان ۲٬۰۰۰
ثبت‌نام‌ها ۲٬۶۶۷
ردیف‌های دفتر کل ۶٬۳۲۵
جلسات / رکوردهای حضور ۴۸۰ / ۳۲٬۰۰۴
اعلان‌ها ۵٬۷۲۵
ردیف‌های حسابرسی ۵٬۶۵۸
یادداشت‌ها ۱٬۸۳۷
حجم دیتابیس ۲۵ مگابایت

پر کردن ۲٬۰۰۰ هنرجو با ثبت‌نام‌ها، پرداخت‌ها و ۴۸۰ برگهٔ حضور از طریق HTTP، ۲۷ ثانیه طول کشید.

زمان سرور برای یک درخواست (میانهٔ ۵ بار)

صفحه میلی‌ثانیه سرور
پیشخوان ۳۶٫۶
فهرست هنرجویان (همه) ۹٫۳
فهرست هنرجویان (بدهکاران) ۲۳٫۷
جست‌وجوی هنرجو ۸٫۵
تب‌های پروفایل هنرجو ۳ تا ۶
کلاس‌ها ۹٫۴
صفحهٔ کلاس ۲۴٫۱
ترم‌ها ۵٫۹
فهرست کلاس‌های حضور و غیاب ۱۵٫۸
برگهٔ حضور ۱۴٫۶
اعلان‌ها ۱۳٫۵
لاگ حسابرسی ۱۰٫۳
صفحهٔ پشتیبان ۵٫۸

بار هم‌زمان، هر بار ۳۰ ثانیه

پروسه کاربر صفحه/ثانیه خطا p50 p95 p50 سرور p95 سرور
۱ ۲۵ ۴۰٫۳ ۰ ۶۱۶ ms ۸۰۵ ms ۱۵ ms ۶۸ ms
۱ ۵۰ ۳۹٫۷ ۰ ۱٬۲۲۵ ms ۱٬۵۶۴ ms ۱۵ ms ۶۹ ms
۱ ۱۰۰ ۴۱٫۸ ۰ ۲٬۳۲۸ ms ۲٬۸۶۴ ms ۱۵ ms ۷۰ ms
۴ ۱۰۰ ۱۲۷٫۴ ۰ ۴۷۵ ms ۱٬۹۳۱ ms ۱۸ ms ۹۰ ms

ترکیب بار: ۲۰٪ پیشخوان، ۲۰٪ فهرست هنرجویان با فیلتر و جست‌وجو، ۲۵٪ تب‌های پروفایل، ۸٪ کلاس‌ها و ترم‌ها، ۱۰٪ برگهٔ حضور، ۵٪ اعلان‌ها، ۱۲٪ نوشتن (یادداشت و پرداخت). حافظهٔ پروسهٔ تکی: ۱۹۹ مگابایت قبل از اجرا، ۳۳۰ مگابایت بعد از آن. صفر خطای سمت سرور در همهٔ اجراها.

خواندن اعداد

  • با یک پروسه، خود سرور در میانه در حدود ۱۵ میلی‌ثانیه پاسخ می‌دهد؛ تأخیر سمت کلاینت در ۱۰۰ کاربر، صف شدن جلوی یک هسته است. چهل صفحه در ثانیه یعنی حدود ۲٬۴۰۰ بازدید در دقیقه، بسیار فراتر از ترافیک یک آموزشگاه.
  • چهار worker روی همان لپ‌تاپ توان عبوری را سه برابر و به ۱۲۷ صفحه در ثانیه رساند، در حالی که مولد بار برای همان CPU رقابت می‌کرد. روی یک میزبان اختصاصی WORKERS=auto بگذارید.
  • SQLite در حالت WAL ترکیب نوشتن‌ها (یادداشت، پرداخت، حضور) را بدون خطای busy مدیریت کرد.

توصیه‌ها

  • پشت HTTPS (پروکسی Coolify) مستقر کنید و ORIGIN را بگذارید؛ TZ را درست نگه دارید تا پشتیبان روزانه و «امروز» با ساعت شما بخواند.
  • روی میزبان‌های بیش از یک هسته WORKERS=auto بگذارید.
  • به‌طور منظم پشتیبان رمزشدهٔ خود برنامه را بگیرید و از Volume مسیر /data اسنپ‌شات بگیرید؛ یک بار بازیابی را تست کنید.
  • رمز مدیر را در مدیر رمز نگه دارید؛ با تغییر کارکنان از تنظیمات → حساب کاربری عوضش کنید.