accounts.0008 drops the columns and refuses to run while guest rows exist. Docs describe the current state; tests that only proved guest behaviour are gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
34 KiB
پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز ماندهاند
آخرین بازبینی با کد: ۲۰۲۶-۱۰-۰۵ (۱۳ مهر ۱۴۰۵)؛ با سرورِ زنده: ۲۰۲۶-۱۰-۰۴.
این پروژه تا این تاریخ در حالتِ دمو و ارائهی کارآموزی است، نه محصولِ نهایی. چند چیز عمداً باز گذاشته شده تا توسعهدهندهها، فرانتکار و ارائه کار کنند. هر کدام برای یک سرورِ عمومی با کاربرِ واقعی (کودک، پرداختِ واقعی) خطر است و باید پیش از آمدنِ اولین کاربرِ واقعی بسته شود.
برای agentها و توسعهدهندههای بعدی: این فهرست ادعاست، نه ضمانت؛ ممکن است از این تاریخ به بعد چیزی عوض شده باشد. پیش از بستنِ هر مورد، وضعیتِ فعلیاش را با خودِ سرور یا کد بسنجید (ستونِ «چطور بسنجید»). مورد که بسته شد، از فهرست برداشته میشود و یک خط در «حفاظهایی که هست» (پایینِ همین فایل) میگیرد، با نامِ تستی که آن را نگه میدارد. تاریخ و دلیلِ تغییر در پیامِ کامیت است، نه اینجا.
هر آزمونِ «از بیرون» با curl بدونِ توکن انجام شده و در تاریخِ بالا تأیید شده است.
خلاصه، به ترتیبِ خطر
| # | مورد | خطر | کارِ کیست |
|---|---|---|---|
| ۱ | کلیدِ امضای توکن روی سرور همان رشتهی داخلِ ریپو است | بحرانی — جعلِ ورود بهجای هر کاربر | کد آماده؛ اعمال روی سرور |
| ۲ | ۷ حسابِ دمو با کدِ ورودِ ثابت روی پروداکشن | بالا | .env سرور |
| ۳ | دادهی دمو روی دیتابیسِ پروداکشن | بالا | دیتابیسِ سرور |
| ۴ | /api/health جزئیاتِ داخلی را به همه نشان میدهد |
متوسط | کد |
| ۵ | Swagger، ReDoc و schemaی کاملِ API عمومیاند | متوسط | کد |
| ۷ | پنلِ /admin/ روی دامنهی عمومی |
متوسط | nginx + کد |
| ۸ | سرور بدونِ ArvanCloud روی پورتِ ۸۰ هم جواب میدهد | متوسط | ادمینِ سرور |
| ۹ | کشِ مشترک (Redis) نیست؛ محدودیتها per-workerاند | متوسط | .env سرور |
| ۱۱ | کدِ OTP بیهش در دیتابیس و در فهرستِ پنل | کم | کد |
| ۱۲ | هزینهی دستیارِ AI با ساختِ شمارهی تازه (هر شماره ۳ سوالِ رایگان) قابلِ بالا بردن است | کم (مالی) | کد |
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر، بدونِ بکاپِ خودکارِ بیرون از ماشین | کم (داده) | زیرساخت |
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
| ۱۶ | فرانت توکن را در localStorage نگه میدارد |
کم | فرانت |
| ۲۰ | یونیتِ systemd DJANGO_DEBUG=1 و کلیدِ ناامن دارد؛ فقط یک خطِ .env جلوی DEBUG را گرفته |
بالا (نهفته) | ادمینِ سرور |
| ۲۲ | سایتِ فرانت هیچ هدرِ امنیتی ندارد (CSP، frame-ancestors، HSTS) | کم | nginx/فرانت |
| ۲۴ | سقفِ روزانهی ۳۰۰ پیامکِ کلِ سامانه را یک ناشناس پر میکند و ورود با پیامک برای همه بسته میشود | بالا (دسترسپذیری، هزینه) | تصمیمِ طراحی |
| ۲۵ | با دانستنِ شمارهی کسی، ورودِ او تا ۲۴ ساعت بسته میشود | متوسط | تصمیمِ طراحی |
| ۲۶ | پایانِ درس و جزیره به گفتهی کلاینت است (جزیرهی قفل، قلبِ صفر، گزارشِ والدین) | متوسط | کد، بعد از ارائه |
| ۲۷ | کلیدِ جوابِ هر سوال با یک حسابِ دوم درمیآید؛ امتیازِ هر جوابِ درست دو بار داده میشود | کم | تصمیمِ محصولی |
| ۲۸ | استفادهی دوباره از توکنِ تمدیدِ دزدیدهشده تشخیص داده نمیشود؛ «خروج از همهی دستگاهها» نیست | کم | کد، بعد از ارائه |
| ۲۹ | پیشنهادِ ثبتنام با حذفِ حساب و ورودِ دوباره با همان شماره دوباره داده میشود | کم | کد |
| ۳۰ | چند مسیرِ خواندنی (از جمله بانکِ سوال، بیورود) throttle ندارند | کم | کد، بعد از ارائه |
| ۳۱ | لپتاپِ توسعه با mobile-preview روی شبکهی محلی باز است و در DEBUG کدِ 0000 را میپذیرد |
کم | لپتاپِ توسعه |
۱. کلیدِ امضای توکن (DJANGO_SECRET_KEY) روی سرور
- چیست: توکنِ ورود فقط
user_idِ امضاشده باdjango.core.signingاست (accounts/authentication.py). امضا باDJANGO_SECRET_KEYانجام میشود. روی سرور این مقدار هنوز همان fallbackِdjango-insecure-…است که درconfig/settings.pyکامیت شده (آخرین تأیید: ۲۰۲۶-۰۹-۲۴، بهصورتِ بولین). - خطر: هر کس ریپو را ببیند (Gitea، zipِ تحویلی، هر کپی) میتواند برای هر
user_idتوکنِ معتبر بسازد و بهجای آن کاربر وارد شود؛ از جمله خریدِ اشتراک، دیدنِ گفتگوها و دادهی خانواده. - چرا باز مانده: عوض کردنش همهی کاربرهای واردشده (توسعهدهندهها، فرانتکار، حسابهای دمو در حالِ ارائه) را بیرون میاندازد. تصمیمِ صاحبِ پروژه: بعد از ارائه.
- رفع:
DJANGO_SECRET_KEYرا از.envو از یونیتِ systemd حذف کنید تاconfig/settings.pyیک کلیدِ تصادفی بسازد و در.django_secret_key(مجوز0600) نگه دارد. سرویس را ریاستارت کنید. همه یک بار دوباره وارد میشوند. - چطور بسنجید:
manage.py check --deployنبایدsecurity.W009بدهد؛ روی سرورvenv/bin/python -c "from django.conf import settings; print(settings.SECRET_KEY.startswith('django-insecure'))"بایدFalseچاپ کند (خودِ کلید را چاپ نکنید).
۲. حسابهای دمو با کدِ ورودِ ثابت (TEST_LOGIN_PHONES)
- چیست: ۷ شمارهی
09120000101تا09120000107با کدِ ثابت در.envسرور. شمارهها در ریپو (docs/operations.md،CLAUDE.md) آمدهاند؛ کدها فقط در راهنمای ارائهی صاحبِ پروژهاند. - خطر: کسی که کد را از راهنما، اسکرینشات یا ارائه ببیند وارد آن حسابها میشود. این
کدها با
OTP_MAX_FAILED_ATTEMPTSباطل نمیشوند (ردیفِOTPCodeندارند)؛ فقط سقفِ per-workerِ تلاشِ ورود جلویشان است. - رفع:
TEST_LOGIN_PHONESرا از.envسرور پاک کنید و ریاستارت کنید. - چطور بسنجید:
/api/healthدیگر هشدارِ «شمارههای تستِ ورود با کدِ ثابت فعالاند» ندارد.
۳. دادهی دمو روی دیتابیسِ پروداکشن
- چیست: کاربرهای ۱۰ تا ۱۵ و ۲۵ (حسابهای دمو با کدِ ثابت، بند ۲) با درس، گفتگو، تیکت و سفارش روی دیتابیسِ پروداکشن ساخته شدهاند. حسابِ ۱۰ («علی رضایی»، با ۷ سفارش) حسابِ نمایشیِ اصلی است.
- خطر: دادهی ساختگی در آمار و جدولِ رتبهبندیِ واقعی.
- رفع: پیش از کاربرِ واقعی با
delete_users --ids … --yes(پیش از آن بکاپ) پاک شوند.purge_demo_dataاینها را نمیگیرد (از مسیرِ واقعیِ viewها ساخته شدهاند). - چطور بسنجید:
/api/health→traffic.usersفقط کاربرانِ واقعی را بشمارد.
۴. /api/health عمومی و پرجزئیات
- چیست:
config/health.py،AllowAny. بدونِ توکن برمیگرداند:debug،warnings(مثلاً «شمارههای تست فعالاند»، «محدودیتِ نرخ بینِ workerها مشترک نیست»)،integrations(کدام سرویسها پیکربندیاند، وضعیتِ اعتبارِ AI، نامِ مدل)،traffic(تعدادِ کاربر، فعالِ ۲۴ ساعتِ اخیر، زمانِ آخرین فعالیت)،allowedOrigins،clientIp، شمارِ محتوا و مهاجرتها. - خطر: نقشهی حمله: مهاجم میفهمد محدودیتها سهبرابر دورزدنیاند، حسابِ کدِ ثابت هست، اعتبارِ AI کی تمام شده، و ترافیکِ واقعی چقدر است.
- چرا باز مانده: صاحبِ پروژه و agentها بدونِ دسترسیِ SSH از همین صفحه سلامتِ سرور را میبینند.
- رفع: پاسخِ عمومی فقط
{"status": "ok"|"degraded"}؛ بقیه پشتِ هدرِX-Internal-Key(INTERNAL_SERVICE_KEY) یا فقط برای staff. - چطور بسنجید:
curl https://mathapi.tadrisino2.ir/api/healthبدونِ هدر نبایدwarningsوtrafficداشته باشد.
۵. مستنداتِ API عمومی
- چیست:
/api/docs/(Swagger)،/api/redoc/و/api/schema/بدونِ ورود بازاند (تأیید: HTTP 200). schema همهی endpointها، پارامترها، کدهای خطا و هدرهای داخلی (X-Internal-Key) را فهرست میکند. - خطر: شناساییِ کاملِ سطحِ حمله بدونِ زحمت.
- چرا باز مانده: قراردادِ مشترک با فرانت همینجاست؛ فرانتکار و ارائه به آن نیاز دارند.
- رفع: مسیر را در nginx به IPهای تیم ببندید، یا در
SPECTACULAR_SETTINGSهر دوِSERVE_PERMISSIONS = ['rest_framework.permissions.IsAdminUser']وSERVE_AUTHENTICATION = ['rest_framework.authentication.SessionAuthentication']را بگذارید.IsAdminUserبهتنهایی کار نمیکند: احرازِ پیشفرضِ DRF فقط توکنِ Bearer است، پس ادمینِ واردشده به پنل هم ۴۰۱ میگیرد (آزموده شد). اسکریپتِ Swagger/ReDoc نسخهی ثابت دارد (نه@latest)، چون روی همان دامنهی/admin/اجرا میشود. - چطور بسنجید:
curl -o /dev/null -w "%{http_code}" …/api/schema/باید ۴۰۱/۴۰۳ بدهد.
۷. پنلِ ادمین روی دامنهی عمومی
- چیست:
/admin/رویmathapi.tadrisino2.ir، با ورودِ نامکاربری/رمزِ جنگو. سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (AdminLoginThrottleMiddleware)، ولی: شمارش per-worker است (مورد ۹)، IP با XFF جعلپذیر است (مورد ۸)، و ۲FA ندارد. - خطر: ادمین همهی محتوا، پلنها، کدهای تخفیف، سفارشها و کاربرها را کنترل میکند.
- رفع: محدود کردنِ
/admin/در nginx به IPهای تیم یا VPN؛ تغییرِ مسیرِ پیشفرض؛ ۲FA؛ رمزهای قوی و حسابِ staffِ جدا برای هر نفر.
۸. دسترسیِ مستقیم به پورتِ ۸۰ (دور زدنِ ArvanCloud)
-
چیست:
http://78.38.35.249/api/healthباHost: mathapi.tadrisino2.irپاسخ میدهد (تأیید: HTTP 200).NUM_PROXIES=2فرض میکند همیشه دو پروکسی جلوی ماست. -
خطر: کسی که IP را بداند
X-Forwarded-Forرا جعل میکند و محدودیتهای IP-محور (OTP، ادمین) را دور میزند. -
رفع (ادمینِ سرور): پورتِ ۸۰/۴۴۳ فقط برای رنجهای ArvanCloud، یا
set_real_ip_from+real_ip_headerدر nginx. -
بازتولید شد ۲۰۲۶-۱۰-۰۴:
curl -H 'Host: mathapi.tadrisino2.ir' -H 'X-Forwarded-For: 1.2.3.4' http://78.38.35.249/api/healthجواب دادclientIp: 1.2.3.4. یعنی هر سقفِ IP-محور (ورودِ ادمین ۱۰ در ۱۵ دقیقه، درخواست و ورودِ OTP) با عوض کردنِ این هدر در هر درخواست دور زده میشود، و حفاظتِ DDoS/WAFِ آروان هم برای این مسیر وجود ندارد. سقفهای per-phone و per-code که در دیتابیس شمرده میشوند متأثر نیستند. پورتهای ۸۰ و ۴۴۳ هر دو باز بودند.
۹. نبودِ کشِ مشترک (CACHE_URL)
- چیست: بدونِ
CACHE_URLهر worker کشِLocMemCacheخودش را دارد (۳ worker). - خطر: همهی محدودیتهای مبتنی بر کش تا سه برابر شلترند: throttleهای DRF، cooldown و سقفِ درخواستِ OTP، سقفِ تلاشِ ورودِ OTP بهازای شماره، سقفِ ورودِ ادمین؛ و باطلشدنِ کش بعد از تغییرِ ادمین فقط در یک worker. (سقفِ حدسِ هر کدِ OTP در دیتابیس است و متأثر نیست.)
- رفع:
CACHE_URL=redis://127.0.0.1:6379/1(db0 مالِ اپِ دیگری است) و ریاستارت. - چطور بسنجید:
/api/health→checks.cache.backendنبایدLocMemCacheباشد.
۱۱. کدِ OTP بیهش در دیتابیس
- چیست:
OTPCode.codeمتنِ ساده است و در فهرستِ پنلِ ادمین (OTPCodeAdmin) دیده میشود. توکنِ تمدید (Session.refresh_token) هششده است و اینجا شامل نیست. - چرا باز مانده: تیم برای آزمایش بیپیامک یک ردیفِ
OTPCodeدر پنل میسازد و کدش را میبیند. - خطر: کسی که پنل یا دیتابیس را ببیند، در چند دقیقهی عمرِ هر کد میتواند با آن وارد شود.
- رفع: ذخیرهی هش و حذفِ ستون از فهرستِ پنل، همراه با راهِ دیگری برای ورودِ آزمایشی.
۱۲. هزینهی دستیارِ AI
- چیست: هر کاربر روزی ۳ سؤالِ رایگان دارد (
AI_DAILY_QUOTA_FREE) و بعد فقط با اعتبارِ خریدهشده؛ ۲۰ در ساعت. هر شمارهی تازه ۳ سؤالِ رایگان میگیرد و ساختنِ شماره با سقفِ پیامک (بند ۲۴) محدود است. سقفِ روزانهی کلِ سامانه (AI_GLOBAL_DAILY_MAX، ۳۰۰۰) خرجِ کل را محدود میکند. طولِ سؤال حداکثر ۲۰۰۰ نویسه است. - خطر: با چند IP، اعتبارِ GapGPT در چند ساعت تمام میشود (اعتبار یک بار در ۲۰۲۶-۰۹-۲۵ به صفر رسید، البته به دلیلِ دیگر).
- رفع: سقفِ سراسریِ روزانه برای کلِ اپ؛ دستیار فقط برای کاربرِ واردشده با شماره یا
با سقفِ بسیار کمتر برای مهمان؛ هشدار وقتی
aiProvider.stateدر/api/healthبرابرِquota_exhaustedشد.
۱۴. دیتابیس و بکاپ
- چیست: SQLite در
~/mathapp-v2/db.sqlite3، روی دیسکی که بینِ چند سرویس مشترک است و ~۸۳٪ پر است.transaction_mode: IMMEDIATEهمزمانی را درست ولی سریالی میکند. بکاپ دستی است (deploy.shپیش ازmigrateبکاپ نمیگیرد) و در همان پوشه میماند. - خطر: از دست رفتنِ کلِ داده با یک خطای دیسک یا یک دستورِ اشتباه؛ پُر شدنِ دیسک سرویسهای دیگر (Gitea، ایمیل) را هم میخواباند.
- رفع: مهاجرت به Postgres (روی همان ماشین نصب است)، بکاپِ خودکار پیش از هر
migrate، و نگهداریِ بکاپ بیرون از ماشین.
۱۵. دسترسیِ سرور
- چیست: ورود با رمز به حسابِ مشترکِ
administratorباsudo (ALL : ALL) ALLو عضوِdockerوlxd، روی ماشینی که Gitea و ایمیلِ شرکت هم رویش است. رمز یک بار در گفتگو با agent گذاشته شده و روی دسکتاپِ صاحبِ پروژه در یک فایلِ متنی نگه داشته میشود. - خطر: هر نشتِ رمز یعنی دسترسیِ root به کلِ ماشینِ شرکت، نه فقط این پروژه.
۲۰۲۶-۱۰-۰۴:
PasswordAuthentication yesدرsshd_configو پورتِ ۲۲ از اینترنت باز است؛ پس رمز از هر جای اینترنت حدسزدنی است (fail2ban سنجیده نشد). - رفع (ادمینِ سرور): کلیدِ SSH بهجای رمز، تعویضِ رمز، حسابِ جدا برای این پروژه با
دسترسیِ فقط به
~/mathapp-v2و یک خطِNOPASSWDفقط برایsystemctl restart zareian-mathapp؛ پاک کردنِ فایلِ رمز از دسکتاپ (Keychain جایگزین است).
۱۶. نگهداریِ توکن در فرانت
- چیست: کلاینتِ فرانت (
src/api/index.tsدر مخزنِ فرانت) توکن و refresh را درlocalStorageنگه میدارد. - خطر: هر XSS در سایت هر دو را میخواند.
- رفع: CSPِ سختگیرانه روی
math.tadrisino2.ir؛ در بلندمدت refresh در کوکیِHttpOnly.
۲۰. یونیتِ systemd: DEBUG و کلیدِ ناامن
- چیست:
systemctl cat gunicorn-mathappهنوزEnvironment="DJANGO_DEBUG=1"وEnvironment="DJANGO_SECRET_KEY=django-insecure-…"دارد.EnvironmentFile=.envبعد از آن میآید و.envخطِDJANGO_DEBUG=0دارد (شمرده شد، مقدار چاپ نشد)؛ برای همین الان DEBUG خاموش است. - خطر: اگر روزی آن یک خط از
.envپاک شود (مثلاً با این فکر که «پیشفرضِ DEBUG خاموش است»)، بعد از ریاستارت DEBUG روشن میشود و کدِ0000برای هر شمارهای پذیرفته میشود.deploy.shاین را نمیبیند، چون تنظیماتِ مؤثر را با محیطِ شِل میخواند نه محیطِ یونیت. - رفع (ادمینِ سرور، با بکاپِ یونیت): هر دو خطِ
Environment=را از یونیت بردارید،daemon-reload، ریاستارت. حذفِ خطِ کلید بهتنهایی کسی را بیرون نمیاندازد اگر.envهمان کلید را داشته باشد؛ عوض کردنِ خودِ کلید (مورد ۱) جداست.
۲۲. هدرهای امنیتیِ فرانت
- چیست:
https://math.tadrisino2.ir/فقطx-xss-protectionدارد؛ نه CSP، نهX-Frame-Options/frame-ancestors، نه HSTS، نهX-Content-Type-Options. API همهی اینها را جز HSTS و CSP دارد. - خطر: صفحهی اپ را میشود در iframeِ سایتِ دیگری گذاشت (clickjacking)؛ و چون توکن در
localStorageاست (مورد ۱۶)، نبودِ CSP اثرِ هر XSSِ احتمالی را کامل میکند. در خودِ فرانت سینکِ XSS پیدا نشد (تنهاdangerouslySetInnerHTMLروی SVGِ ثابتِ شکلکهاست). - رفع: در nginxِ
mathapp-frontیا در آروان:Content-Security-Policyباframe-ancestors 'none'وconnect-srcبه دامنهی API، وX-Content-Type-Options: nosniff.
۲۴. پر کردنِ سقفِ سراسریِ پیامک
- چیست:
OTP_SMS_GLOBAL_DAILY_MAX(۳۰۰ در ۲۴ ساعت) جلوی خرجِ بیسقفِ پیامک را میگیرد. هر ناشناسی با درخواستِ کد برای شمارههای تصادفی آن را پر میکند؛ از یک IP بیجعل ساعتی ۲۰ درخواست، یعنی روزی ۴۸۰، پس یک نفر سقف را همیشه پر نگه میدارد. - بازتولید شد: ۳۰۰ درخواست پذیرفته و ۳۰۰ پیامک فرستاده شد؛ بعد درخواستِ کاربرِ واقعی ۵۰۳.
- خطر: هیچ ثبتنامِ تازه، ورودِ دوباره یا حذفِ حسابی ممکن نیست؛ هزینهی ۳۰۰ پیامک در روز.
- رفع (تصمیمِ طراحی): بعد از گذشتن از یک حد، CAPTCHA یا مانعِ مشابه پیش از ارسال؛ شمارشِ سقف بر اساسِ IP/شبکه؛ هشدار در نیمهی سقف.
۲۵. بستنِ ورودِ یک شمارهی مشخص
- چیست: ۱۰ درخواستِ کد در ~۲ ساعت سقفِ روزانهی آن شماره را پر میکند (صاحبش تا ۲۴ ساعت کد نمیگیرد)، و ۵ حدسِ غلطِ هر کسی کدِ تازهی او را باطل میکند. هر دو بازتولید شد.
- رفع (تصمیمِ طراحی): کد به درخواستکننده گره بخورد (درخواستِ کد یک شناسهی موقت برگرداند که ورود لازمش دارد) و حدسِ غلط برای هر درخواستکننده جدا شمرده شود.
۲۶. پایانِ درس و جزیره به گفتهی کلاینت
- چیست:
POST /api/nodes/{id}/quiz/completeوPOST /api/lessons/{id}/completeتعدادِ سوال و جوابِ درست را از کلاینت میگیرند. بازتولید شد: با قلبِ صفر وtotalQuestions=1جزیره تمام میشود؛ جزیرهی قفل باcorrectCount=0تمام میشود؛ درس و جزیره بیهیچ جوابی تمام میشوند، پسGET /api/user/weekly-reportپیشرفتِ ۱۰۰٪ نشان میدهد بیآنکه سوالی جواب داده شده باشد. - چرا باز مانده: رفتارِ آزمون در فرانت به همین شکل است؛ عوض کردنش پیش از ارائه جریانِ نقشه را به خطر میاندازد.
- رفع: تعدادِ سوالِ آزمون را سرور تعیین کند (همان
/quizبا seed)، همان تعداد جوابِ ثبتشده لازم باشد، و جزیرهی قبلی تمام شده باشد.
۲۷. کلیدِ جواب با حسابِ دوم، امتیازِ دوباره
- چیست: جوابِ غلط
correctOptionIdرا برمیگرداند و برای همه یکی است؛ پایانِ درس و پایانِ جزیره هر کدام جدا امتیاز میدهند. یک مهمانِ دورریختنی کلید را درمیآورد و حسابِ اصلی همه را درست میزند (۴ سوال، ۸۰ امتیاز). رتبهبندی و آمارِ درصدِ غلط آلوده میشوند. - رفع (تصمیمِ محصولی): امتیازِ هر سوال یک بار؛ گزینهی درست فقط بعد از پایانِ آزمون.
۲۸. توکنِ تمدیدِ دزدیدهشده
- چیست: استفادهی دوباره از یک توکنِ تمدیدِ باطلشده فقط ۴۰۱ میگیرد؛ اگر دزد اول تمدید کند، صاحبِ حساب بیرون میافتد و دزد میماند. «خروج از همهی دستگاهها» هم نیست.
- چرا باز مانده: باطل کردنِ کلِ زنجیره با استفادهی دوباره، حسابهای دمو را که در ارائه چند نفر همزمان با آنها کار میکنند بیرون میاندازد.
- رفع: شناسهی خانواده روی
Session؛ با استفادهی دوباره کلِ خانواده باطل شود؛ گزینهی خروج از همهجا.
۲۹. پیشنهادِ ثبتنام بعد از حذفِ حساب
- چیست: پیشنهادِ خودکارِ ثبتنام به
user_idگره خورده است؛ حذفِ حساب و ورودِ دوباره با همان شماره یک کاربرِ تازه میسازد و پیشنهاد دوباره داده میشود. فقط وقتی اثر دارد که یک Offerِ ثبتنام فعال باشد. - رفع: گره زدنِ پیشنهادِ ثبتنام به هشِ شماره.
۳۰. مسیرهای خواندنیِ بیthrottle
- چیست:
lessons/{id}/questionsوlessons/{id}/intro(بیورود)،progress/nodes،user/hearts،notifications/{id}/read،support/tickets/{id}و چند مسیرِ دیگرthrottle_scopeندارند؛ کلِ بانکِ سوال بیورود قابلِ کپی است. (/api/schema/سقفِreadدارد.) - رفع: برای هر کدام
throttle_scopeو ردیفِ نرخ درconfig/settings.pyاضافه شود.
۳۱. لپتاپِ توسعه روی شبکهی محلی
- چیست:
mobile-preview/serve.pyعمداً روی کلِ شبکه گوش میدهد (برای گوشیِ روی همان وایفای) و/api/را به runserverِ محلی میفرستد؛ در DEBUG کدِ0000هر شمارهای را وارد میکند و کلیدِ GapGPTِ لپتاپ همان حسابِ سرور است. (docker composeفقط روی127.0.0.1باز است و کلیدِ سرویسِ داخلی دیگر پیشفرض ندارد.) - رفع: روی شبکهی عمومی (کافه، مدرسه) اجرا نشود؛ برای توسعه کلیدهای جدای GapGPT و Qdrant.
حفاظهایی که هست (تا دوباره باز نشوند)
هر تغییری در این مسیرها باید تستهای نامبرده را سبز نگه دارد. جزئیاتِ پرداخت و تخفیف در
docs/architecture.md.
| حفاظ | تست |
|---|---|
ورودِ مهمان وجود ندارد؛ مدلِ User ستونِ is_guest/device_id ندارد و migrationِ حذفشان تا مهمانی مانده باشد میشکند |
accounts/migrations/0008_remove_guest_login.py |
| پلنِ هر خرید روی سفارش؛ خرید اشتراکِ فعال را دست نمیزند؛ یک کدِ تخفیف پیش از درگاه؛ امتیاز فقط از جوابِ ثبتشده | payment/tests_audit_fixes.py، accounts/tests_audit_fixes.py |
لینکِ پرداخت (/api/payment/orders/{id}/pay) فقط با امضای کوتاهعمرِ همراهِ paymentUrl باز میشود |
payment/tests.py (PaymentRedirectViewTests) |
| توکنِ تمدید فقط بهصورتِ هشِ SHA-256 ذخیره میشود | accounts/tests.py (RefreshTokenStoredHashedTests) |
| جدولِ رتبهبندی فقط برای کاربرِ واردشده | progress/tests.py (LeaderboardPeriodValidationTests) |
| شمارهی موبایل نرمال میشود؛ cooldown، سقفِ روزانهی هر شماره و سقفِ کلِ پیامک در دیتابیس شمرده میشوند | accounts/tests_otp_limits.py |
| OTPِ ۶ رقمی با ۵ حدس برای هر کد؛ خروج از حساب توکنهای قبلی را باطل میکند | accounts/tests_audit_fixes.py |
| قلبِ صفر جوابِ سوال را میبندد؛ چکش داخلِ تراکنش است (پایانِ جزیره را فقط کامل نمیبندد، مورد ۲۶) | content/tests_hearts_gate.py |
select_for_update مؤثر با transaction_mode: IMMEDIATE؛ مقایسهی زمانثابتِ X-Internal-Key |
payment/tests_audit_fixes.py |
دیتابیس و بکاپها فقط برای صاحبِ سرویس خواندنیاند (مرحلهی «مجوزِ فایلهای داده» در deploy.sh) |
با sudo -u nobody head -c 15 ~/mathapp-v2/db.sqlite3 روی سرور باید خطای دسترسی بدهد |
کتابخانهها بدونِ آسیبپذیریِ شناختهشده (urllib3>=2.8.0، python-dotenv 1.2.2) |
پایگاهِ OSV روی pip freeze |
| actionهای لغو/بازپرداختِ سفارش، علامتِ «بررسیشده» و لینکِ بازار اجازهی تغییر میخواهند؛ کارمندِ فقطخواندنی (Supervisor) اجرایشان نمیکند | payment/tests_security_review.py، content/tests_security_review.py |
| ورودِ فایلِ سوال/برنامهی درسی اجازهی افزودن (و برای جایگزینی، حذف) میخواهد؛ مرورگرِ محتوا اجازهی دیدنِ سوال | content/tests_security_review.py |
| حذفِ حساب سابقهی کدِ تخفیف، پیشنهادها و کدهای پیامکیِ شماره را نگه میدارد؛ حسابِ دمو حذف نمیشود | accounts/tests_security_review.py |
| صافیِ اطلاعاتِ تماس در نامِ نمایشی (شماره با هر جداکننده، آیدیِ شبکهی اجتماعی) | accounts/tests_security_review.py |
| کلیدِ کاوهنگار و توکنِ خریدِ بازار در لاگِ خطای شبکه نمیآیند | accounts/tests_security_review.py |
برگشت از زرینپال بیAuthority ۴۰۰؛ خریدِ دومِ بازار روی سفارشِ پرداختشده ۴۰۹؛ آنچه داده یا پس گرفته میشود از خودِ سفارش است، نه پلنِ فعلی |
payment/tests_security_review.py |
شکلِ خرابِ مدل جواب را رایگان نمیکند؛ علامتِ «خارج از ریاضی» در هر جای استریم جواب را رد میکند و در کش نمیرود؛ بستنِ صفحه جوابِ ناقص را ثبت میکند؛ collectionِ بیرون از KNOWN_COLLECTIONS جستجو نمیشود |
ai_tutor/tests_security_review.py |
ورودیِ عددیِ غیرِ ASCII (مثلِ ²) ۴۰۰ است نه ۵۰۰؛ متنِ تیکت/گزارشِ باگ حداکثر ۵۰۰۰ نویسه |
content/tests_security_review.py |
INTERNAL_SERVICE_KEY در هیچ حالتی پیشفرض ندارد |
payment/tests.py (test_debug_mode_has_no_default_internal_key) |
کلیدِ امضای خودساخته (.django_secret_key) یک بار و اتمی ساخته میشود؛ سه workerِ همزمان یک کلید دارند |
دستی: سه پروسهی همزمان که config.settings را import میکنند، بیفایلِ کلید؛ ۲۰ از ۲۰ یک کلید |
| فرانت: شمارهی سفارشِ برگشتی از درگاه فقط عدد است، مسیرها encode میشوند و «پرداخت موفق» فقط از پاسخِ سرور میآید؛ دو تبِ همزمان با تمدیدِ توکن خارج نمیشوند | مخزنِ فرانت: src/app/launchParams.ts، src/api/http.ts |
| سفارش و تراکنش در پنل کاملاً فقطخواندنیاند (کارمندِ لغو/بازپرداخت مبلغ یا اعتبارِ سفارش را عوض نمیکند) و از پنل ساخته نمیشوند | payment/tests_security_review.py (OrderAdminMoneyFieldsReadOnlyTests) |
| ردِ بازار توکنِ خرید را نمیسوزاند؛ سفارشِ بازپرداختشده توکنِ تازه نمیپذیرد | payment/tests_security_review.py (BazaarRejectedVerifyDoesNotBurnTokenTests) |
| کدِ تخفیفِ وسطِ رفتن به درگاه، مبلغِ authority را با سفارش ناهمخوان نمیکند | payment/tests_security_review.py (DiscountDuringPaymentRequestTests) |
| پرداختِ بستهای که پلنش پاک شده از عددهای ثبتشدهی سفارش اعتبار میدهد (و اگر نداشت ERROR لاگ میشود)؛ پلن فقط با IRT ذخیره میشود | subscription/tests_security_review.py (PaidPackWithDeletedPlanTests، PlanCurrencyIsTomanOnlyTests) |
توکنِ بازار (refresh_token، pending_state) با فیلترِ querystringِ پنل حرفبهحرف درنمیآید |
payment/tests_security_review.py (BazaarTokenNotGuessableByAdminFilterTests) |
| شمارهی کاربر را فقط ابرکاربر عوض میکند؛ حسابهای کارمند و گروهها فقط با ابرکاربر ساخته/تغییر میکنند | accounts/tests_security_review.py (StaffCannotSwapLoginPhoneTests، StaffAccountsAdminSuperuserOnlyTests) |
| لاگِ تحویلِ کمپین اجازهی view میخواهد (هر کارمندی نمیبیند) | campaigns/tests_security_review.py |
| هر رقمِ یونیکد (تمامعرض، ریاضی…) در شماره به لاتین برمیگردد؛ کدِ غیرِ ASCII، پایهی ناموجود یا عظیم و تاریخِ تولدِ با «²» ۴۰۰ است نه ۵۰۰؛ صافیِ نامِ نمایشی ارقامِ یونیکد و جداکنندهی دلخواه را میگیرد | accounts/tests_security_review.py (UnicodeInputsAreNormalisedOrRejectedTests) |
/api/home?gradeId= ناموجود کش نمیشود (کشِ پُر شمارندهی سقفِ ورودِ ادمین را پاک میکرد)؛ /api/progress/nodes یک کوئری است و unit عظیم ۴۰۰ |
content/tests_security_review.py (JunkGradeIdsDoNotFlushCacheTests، ProgressNodesInputAndQueriesTests) |