list everything left open for the demo that must be closed before real users
docs/security-before-production.md records, item by item, what is exposed on purpose while this is an internship demo: the committed token-signing key still in use on the server, seven fixed-code demo logins, demo users in the production database, a public /api/health that reports internal state, a public API schema, a public leaderboard with children's names, /admin on the public domain, the server answering on port 80 around ArvanCloud, per-worker rate limits without Redis, guest login by deviceId alone, unhashed refresh tokens and OTP codes, AI cost exposure through throwaway guests, hearts not enforced, SQLite without off-box backups, and shared root access to the box. Each has its risk, why it was left, the fix and how to verify it; the exposure checks were run against the live server on 2026-09-25. CLAUDE.md, README and docs/operations.md point to it so no agent mistakes this deployment for production-ready. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,15 @@ at checkout, `select_for_update` guards that SQLite silently ignores).
|
|||||||
race-free / the right architecture"; write "guards X; not tested under Y" instead.
|
race-free / the right architecture"; write "guards X; not tested under Y" instead.
|
||||||
Record uncertainty plainly so the next reader re-checks rather than trusts.
|
Record uncertainty plainly so the next reader re-checks rather than trusts.
|
||||||
|
|
||||||
|
## Before any real user: read `docs/security-before-production.md`
|
||||||
|
|
||||||
|
The server is deliberately left in demo mode for the internship presentation: the token
|
||||||
|
signing key is the one committed in this repo, seven fixed-code demo logins exist, demo
|
||||||
|
users sit in the production DB, and `/api/health`, the API schema, the leaderboard and
|
||||||
|
`/admin/` are all public. Each is listed there with its risk, why it was left, and the
|
||||||
|
fix. **Do not treat this deployment as production-ready until every item there is closed**,
|
||||||
|
and record the date and commit of each fix in that file rather than deleting the item.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ curl https://api.math.tadrisino2.ir/api/health
|
|||||||
| سند | موضوع |
|
| سند | موضوع |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`docs/operations.md`](docs/operations.md) | دیپلوی، متغیرهای محیطی، سلامت، بکاپ، ورودِ توسعهدهندهها |
|
| [`docs/operations.md`](docs/operations.md) | دیپلوی، متغیرهای محیطی، سلامت، بکاپ، ورودِ توسعهدهندهها |
|
||||||
|
| [`docs/security-before-production.md`](docs/security-before-production.md) | **پیش از کاربرِ واقعی:** هرچه برای دمو عمداً باز مانده، خطرش و راهِ بستنش |
|
||||||
| [`docs/content-authoring.md`](docs/content-authoring.md) | افزودن و ویرایشِ محتوا از پنلِ ادمین، ساختِ انبوهِ سوال |
|
| [`docs/content-authoring.md`](docs/content-authoring.md) | افزودن و ویرایشِ محتوا از پنلِ ادمین، ساختِ انبوهِ سوال |
|
||||||
| [`docs/architecture.md`](docs/architecture.md) | تصمیمهای معماری و دلیلشان |
|
| [`docs/architecture.md`](docs/architecture.md) | تصمیمهای معماری و دلیلشان |
|
||||||
| [`docs/frontend/README.md`](docs/frontend/README.md) | راهنمای اتصالِ فرانتاند + کلاینتِ تایپدار |
|
| [`docs/frontend/README.md`](docs/frontend/README.md) | راهنمای اتصالِ فرانتاند + کلاینتِ تایپدار |
|
||||||
|
|||||||
@@ -230,3 +230,10 @@ sudo systemctl restart gunicorn # مالِ ما نیست — اپِ
|
|||||||
|
|
||||||
هر خطای پیشبینینشده همیشه پاسخِ JSON برمیگرداند — نه صفحهی HTML پیشفرضِ
|
هر خطای پیشبینینشده همیشه پاسخِ JSON برمیگرداند — نه صفحهی HTML پیشفرضِ
|
||||||
جنگو — و کامل با traceback لاگ میشود (`config/exceptions.py`).
|
جنگو — و کامل با traceback لاگ میشود (`config/exceptions.py`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **پیش از آمدنِ کاربرِ واقعی** فهرستِ [`security-before-production.md`](security-before-production.md)
|
||||||
|
> را کامل ببندید: کلیدِ امضا، حسابهای دمو، جزئیاتِ عمومیِ `/api/health`، مستنداتِ عمومیِ API،
|
||||||
|
> جدولِ رتبهبندیِ عمومی، پنلِ ادمین، پورتِ ۸۰ و دسترسیِ سرور.
|
||||||
|
|
||||||
|
|||||||
220
docs/security-before-production.md
Normal file
220
docs/security-before-production.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز ماندهاند
|
||||||
|
|
||||||
|
**تاریخِ ثبت: ۲۰۲۶-۰۹-۲۵ (۳ مهر ۱۴۰۵)، کامیتِ پایه `66066cb`.**
|
||||||
|
|
||||||
|
این پروژه تا این تاریخ در حالتِ **دمو و ارائهی کارآموزی** است، نه محصولِ نهایی. چند چیز
|
||||||
|
عمداً باز گذاشته شده تا توسعهدهندهها، فرانتکار و ارائه کار کنند. هر کدام برای یک سرورِ
|
||||||
|
عمومی با کاربرِ واقعی (کودک، پرداختِ واقعی) **خطر است** و باید پیش از آمدنِ اولین کاربرِ
|
||||||
|
واقعی بسته شود.
|
||||||
|
|
||||||
|
**برای agentها و توسعهدهندههای بعدی:** این فهرست ادعاست، نه ضمانت؛ ممکن است از این
|
||||||
|
تاریخ به بعد چیزی عوض شده باشد. پیش از بستنِ هر مورد، وضعیتِ فعلیاش را با خودِ سرور یا
|
||||||
|
کد بسنجید (ستونِ «چطور بسنجید»). بعد از بستنِ هر مورد، همینجا تاریخ و کامیتِ رفعش را
|
||||||
|
بنویسید؛ مورد را پاک نکنید.
|
||||||
|
|
||||||
|
هر آزمونِ «از بیرون» با `curl` بدونِ توکن انجام شده و در تاریخِ بالا تأیید شده است.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه، به ترتیبِ خطر
|
||||||
|
|
||||||
|
| # | مورد | خطر | کارِ کیست |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ۱ | کلیدِ امضای توکن روی سرور همان رشتهی داخلِ ریپو است | بحرانی — جعلِ ورود بهجای هر کاربر | کد آماده؛ اعمال روی سرور |
|
||||||
|
| ۲ | ۷ حسابِ دمو با کدِ ورودِ ثابت روی پروداکشن | بالا | `.env` سرور |
|
||||||
|
| ۳ | دادهی دمو و حسابِ مهمانِ عمومیِ `frontend-test-device` | بالا | دیتابیسِ سرور |
|
||||||
|
| ۴ | `/api/health` جزئیاتِ داخلی را به همه نشان میدهد | متوسط | کد |
|
||||||
|
| ۵ | Swagger، ReDoc و schemaی کاملِ API عمومیاند | متوسط | کد |
|
||||||
|
| ۶ | جدولِ رتبهبندی نام و شناسهی کودکان را بدونِ ورود نشان میدهد | متوسط (حریمِ خصوصیِ کودک) | کد |
|
||||||
|
| ۷ | پنلِ `/admin/` روی دامنهی عمومی | متوسط | nginx + کد |
|
||||||
|
| ۸ | سرور بدونِ ArvanCloud روی پورتِ ۸۰ هم جواب میدهد | متوسط | ادمینِ سرور |
|
||||||
|
| ۹ | کشِ مشترک (Redis) نیست؛ محدودیتها per-workerاند | متوسط | `.env` سرور |
|
||||||
|
| ۱۰ | ورودِ مهمان فقط با `deviceId` است | کم (طراحی) | کد |
|
||||||
|
| ۱۱ | توکنِ refresh و کدِ OTP بیهش در دیتابیس | کم | کد |
|
||||||
|
| ۱۲ | هزینهی دستیارِ AI با ساختِ حسابِ مهمان قابلِ بالا بردن است | کم (مالی) | کد |
|
||||||
|
| ۱۳ | محدودیتِ قلب در سرور خاموش است (`HEARTS_ENFORCED=0`) | کم (کسبوکار) | `.env` + فرانت |
|
||||||
|
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر، بدونِ بکاپِ خودکارِ بیرون از ماشین | کم (داده) | زیرساخت |
|
||||||
|
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||||||
|
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. کلیدِ امضای توکن (`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` دیگر هشدارِ «شمارههای تستِ ورود با کدِ ثابت فعالاند» ندارد.
|
||||||
|
|
||||||
|
## ۳. دادهی دمو و مهمانِ عمومی
|
||||||
|
|
||||||
|
- **چیست:** کاربرهای ۶ (`deviceId = frontend-test-device`) و ۱۰ تا ۱۵، با درس، گفتگو،
|
||||||
|
تیکت، خانواده و زیرمجموعه، روی دیتابیسِ پروداکشن ساخته شدهاند. `frontend-test-device`
|
||||||
|
در ریپو نوشته شده؛ هر کس `POST /api/auth/session` با همین `deviceId` بزند، توکنِ همان
|
||||||
|
حساب را میگیرد.
|
||||||
|
- **خطر:** دادهی ساختگی در آمار و جدولِ رتبهبندیِ واقعی؛ یک حسابِ عمومی که هر کس
|
||||||
|
میتواند با آن کار کند (ثبتِ تیکت، گفتگو با AI به هزینهی ما).
|
||||||
|
- **رفع:** پیش از کاربرِ واقعی پاک شوند. `purge_demo_data` اینها را **نمیگیرد** (از مسیرِ
|
||||||
|
واقعیِ viewها ساخته شدهاند)؛ باید با شناسه پاک شوند، پیش از آن بکاپ.
|
||||||
|
- **چطور بسنجید:** `POST /api/auth/session` با `frontend-test-device` باید `isNewUser: true` بدهد.
|
||||||
|
|
||||||
|
## ۴. `/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://api.math.tadrisino2.ir/api/health` بدونِ هدر نباید
|
||||||
|
`warnings` و `traffic` داشته باشد.
|
||||||
|
|
||||||
|
## ۵. مستنداتِ API عمومی
|
||||||
|
|
||||||
|
- **چیست:** `/api/docs/` (Swagger)، `/api/redoc/` و `/api/schema/` بدونِ ورود بازاند
|
||||||
|
(تأیید: HTTP 200). schema همهی endpointها، پارامترها، کدهای خطا و هدرهای داخلی
|
||||||
|
(`X-Internal-Key`) را فهرست میکند.
|
||||||
|
- **خطر:** شناساییِ کاملِ سطحِ حمله بدونِ زحمت.
|
||||||
|
- **چرا باز مانده:** قراردادِ مشترک با فرانت همینجاست؛ فرانتکار و ارائه به آن نیاز دارند.
|
||||||
|
- **رفع:** در پروداکشن `SERVE_PERMISSIONS` در `SPECTACULAR_SETTINGS` را به
|
||||||
|
`IsAdminUser` (staffِ جنگو) محدود کنید، یا مسیر را در nginx به IPهای تیم ببندید.
|
||||||
|
- **چطور بسنجید:** `curl -o /dev/null -w "%{http_code}" …/api/schema/` باید ۴۰۱/۴۰۳ بدهد.
|
||||||
|
|
||||||
|
## ۶. جدولِ رتبهبندیِ عمومی — حریمِ خصوصیِ کودک
|
||||||
|
|
||||||
|
- **چیست:** `GET /api/leaderboard` بدونِ توکن (تأیید: HTTP 200) برای هر ردیف `rank`،
|
||||||
|
`userId`، `name` و `xpInPeriod` میدهد.
|
||||||
|
- **خطر:** نامِ کودکان و شناسهی داخلیشان برای همهی اینترنت قابلِ جمعآوری است.
|
||||||
|
پاکسازیِ نام (حذفِ شماره و لینک، `clean_display_name`) فقط راهِ تماس را میگیرد، نه خودِ نام را.
|
||||||
|
- **رفع:** `IsAuthenticated`؛ حذفِ `userId` از پاسخ (جز برای ردیفِ خودِ کاربر)؛ نمایشِ
|
||||||
|
نامِ کوچک یا نامِ مستعار.
|
||||||
|
|
||||||
|
## ۷. پنلِ ادمین روی دامنهی عمومی
|
||||||
|
|
||||||
|
- **چیست:** `/admin/` روی `api.math.tadrisino2.ir`، با ورودِ نامکاربری/رمزِ جنگو. از
|
||||||
|
۲۰۲۶-۰۹-۲۵ سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (`AdminLoginThrottleMiddleware`)،
|
||||||
|
ولی: شمارش per-worker است (مورد ۹)، IP با XFF جعلپذیر است (مورد ۸)، و ۲FA ندارد.
|
||||||
|
- **خطر:** ادمین همهی محتوا، پلنها، کدهای تخفیف، سفارشها و کاربرها را کنترل میکند.
|
||||||
|
- **رفع:** محدود کردنِ `/admin/` در nginx به IPهای تیم یا VPN؛ تغییرِ مسیرِ پیشفرض؛ ۲FA؛
|
||||||
|
رمزهای قوی و حسابِ staffِ جدا برای هر نفر.
|
||||||
|
|
||||||
|
## ۸. دسترسیِ مستقیم به پورتِ ۸۰ (دور زدنِ ArvanCloud)
|
||||||
|
|
||||||
|
- **چیست:** `http://78.38.35.249/api/health` با `Host: api.math.tadrisino2.ir` پاسخ میدهد
|
||||||
|
(تأیید: HTTP 200). `NUM_PROXIES=2` فرض میکند همیشه دو پروکسی جلوی ماست.
|
||||||
|
- **خطر:** کسی که IP را بداند `X-Forwarded-For` را جعل میکند و محدودیتهای IP-محور
|
||||||
|
(مهمان، OTP، ادمین) را دور میزند.
|
||||||
|
- **رفع (ادمینِ سرور):** پورتِ ۸۰/۴۴۳ فقط برای رنجهای ArvanCloud، یا `set_real_ip_from` +
|
||||||
|
`real_ip_header` در nginx.
|
||||||
|
|
||||||
|
## ۹. نبودِ کشِ مشترک (`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` باشد.
|
||||||
|
|
||||||
|
## ۱۰. ورودِ مهمان فقط با `deviceId`
|
||||||
|
|
||||||
|
- **چیست:** `POST /api/auth/session` با هر `deviceId` یا حسابِ مهمانِ همان دستگاه را
|
||||||
|
برمیگرداند یا میسازد (`accounts/views.py::GuestSessionView`). هیچ رازِ دیگری در کار نیست.
|
||||||
|
- **خطر:** هر کس `deviceId`ِ یک مهمان را بداند (از `localStorage`، لاگ، اسکرینشات) وارد
|
||||||
|
حسابِ او میشود. بعد از ثبتنام با شماره این راه بسته میشود (مهمان دیگر مهمان نیست).
|
||||||
|
- **رفع:** برای مهمان هم یک رازِ تصادفیِ سمتِ سرور صادر شود و ورودِ دوباره با همان راز باشد،
|
||||||
|
نه با `deviceId`ِ خام.
|
||||||
|
|
||||||
|
## ۱۱. دادهی حساس بیهش در دیتابیس
|
||||||
|
|
||||||
|
- **چیست:** `Session.refresh_token` و `OTPCode.code` بهصورتِ متنِ ساده ذخیره میشوند
|
||||||
|
(`accounts/models.py`) و در پنلِ ادمین (`Session`, `OTPCode`) دیده میشوند.
|
||||||
|
- **خطر:** هر کس بکاپِ دیتابیس یا پنلِ ادمین را ببیند نشستِ هر کاربر را میرباید.
|
||||||
|
بکاپها در `.deploy-backups/` کنارِ خودِ پروژه روی یک ماشینِ مشترکاند.
|
||||||
|
- **رفع:** ذخیرهی هشِ SHA-256 و مقایسه با هش؛ حذفِ این دو فیلد از نمایشِ ادمین.
|
||||||
|
|
||||||
|
## ۱۲. هزینهی دستیارِ AI
|
||||||
|
|
||||||
|
- **چیست:** سقفِ روزانه ۱۵ سؤال **برای هر کاربر** (`DAILY_AI_QUOTA`) و ۲۰ در ساعت؛ مهمانها
|
||||||
|
بر اساسِ IP محدود میشوند ولی هر IP ساعتی ۲۰ حسابِ مهمانِ تازه میسازد (`guest_session`)،
|
||||||
|
و هر حسابِ تازه ۱۵ سؤالِ تازه دارد. طولِ سؤال از ۲۰۲۶-۰۹-۲۵ حداکثر ۲۰۰۰ نویسه است.
|
||||||
|
- **خطر:** با چند IP، اعتبارِ GapGPT در چند ساعت تمام میشود (اعتبار یک بار در ۲۰۲۶-۰۹-۲۵
|
||||||
|
به صفر رسید، البته به دلیلِ دیگر).
|
||||||
|
- **رفع:** سقفِ سراسریِ روزانه برای کلِ اپ؛ دستیار فقط برای کاربرِ واردشده با شماره یا
|
||||||
|
با سقفِ بسیار کمتر برای مهمان؛ هشدار وقتی `aiProvider.state` در `/api/health` برابرِ
|
||||||
|
`quota_exhausted` شد.
|
||||||
|
|
||||||
|
## ۱۳. محدودیتِ قلب خاموش است
|
||||||
|
|
||||||
|
- **چیست:** با صفر قلب، سرور همچنان جواب را میپذیرد، مگر `HEARTS_ENFORCED=1`
|
||||||
|
(`content/views.py::QuestionAnswerView`). «قلبِ نامحدود» تنها مزیتِ اشتراک است.
|
||||||
|
- **خطر:** مزیتِ پولی فقط در ظاهرِ اپ اعمال میشود؛ با صدا زدنِ مستقیمِ API دور زده میشود.
|
||||||
|
- **چرا باز مانده:** فرانت برای ۴۰۳ِ `no_hearts` هنوز صفحه ندارد؛ روشن کردنش دانشآموز را به
|
||||||
|
صفحهی خطا میبرد. رفتار با صفر قلب هم تصمیمِ محصولیِ باز است.
|
||||||
|
- **رفع:** بعد از صفحهی «قلبها تمام شد» در فرانت، `HEARTS_ENFORCED=1` در `.env`.
|
||||||
|
|
||||||
|
## ۱۴. دیتابیس و بکاپ
|
||||||
|
|
||||||
|
- **چیست:** SQLite در `~/mathapp-v2/db.sqlite3`، روی دیسکی که بینِ چند سرویس مشترک است و
|
||||||
|
~۸۳٪ پر است. `transaction_mode: IMMEDIATE` همزمانی را درست ولی سریالی میکند. بکاپ دستی
|
||||||
|
است (`deploy.sh` پیش از `migrate` بکاپ نمیگیرد) و در همان پوشه میماند.
|
||||||
|
- **خطر:** از دست رفتنِ کلِ داده با یک خطای دیسک یا یک دستورِ اشتباه؛ پُر شدنِ دیسک
|
||||||
|
سرویسهای دیگر (Gitea، ایمیل) را هم میخواباند.
|
||||||
|
- **رفع:** مهاجرت به Postgres (روی همان ماشین نصب است)، بکاپِ خودکار پیش از هر `migrate`، و
|
||||||
|
نگهداریِ بکاپ بیرون از ماشین.
|
||||||
|
|
||||||
|
## ۱۵. دسترسیِ سرور
|
||||||
|
|
||||||
|
- **چیست:** ورود با رمز به حسابِ مشترکِ `administrator` با `sudo (ALL : ALL) ALL` و عضوِ
|
||||||
|
`docker` و `lxd`، روی ماشینی که Gitea و ایمیلِ شرکت هم رویش است. رمز یک بار در گفتگو با
|
||||||
|
agent گذاشته شده و روی دسکتاپِ صاحبِ پروژه در یک فایلِ متنی نگه داشته میشود.
|
||||||
|
- **خطر:** هر نشتِ رمز یعنی دسترسیِ root به کلِ ماشینِ شرکت، نه فقط این پروژه.
|
||||||
|
- **رفع (ادمینِ سرور):** کلیدِ SSH بهجای رمز، تعویضِ رمز، حسابِ جدا برای این پروژه با
|
||||||
|
دسترسیِ فقط به `~/mathapp-v2` و یک خطِ `NOPASSWD` فقط برای
|
||||||
|
`systemctl restart zareian-mathapp`؛ پاک کردنِ فایلِ رمز از دسکتاپ (Keychain جایگزین است).
|
||||||
|
|
||||||
|
## ۱۶. نگهداریِ توکن در فرانت
|
||||||
|
|
||||||
|
- **چیست:** کلاینتِ فرانت (`src/api/index.ts` در مخزنِ فرانت) توکن و refresh را در
|
||||||
|
`localStorage` نگه میدارد.
|
||||||
|
- **خطر:** هر XSS در سایت هر دو را میخواند.
|
||||||
|
- **رفع:** CSPِ سختگیرانه روی `math.tadrisino2.ir`؛ در بلندمدت refresh در کوکیِ `HttpOnly`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## چیزهایی که بسته شدهاند (برای اینکه دوباره باز نشوند)
|
||||||
|
|
||||||
|
در بازبینیِ ۲۰۲۶-۰۹-۲۵ (کامیتِ `42783f9`) بسته شدند؛ جزئیات در `docs/architecture.md`:
|
||||||
|
پلنِ هر خرید روی سفارش، دست نخوردنِ اشتراکِ فعال هنگامِ خرید، یک کدِ تخفیف پیش از درگاه،
|
||||||
|
حذفِ `/api/payment/webhook`، امتیاز فقط از جوابِ ثبتشده، OTPِ ۶ رقمی با ۵ حدس، خروج از حساب،
|
||||||
|
حفظِ مهمان هنگامِ ثبتنام، `select_for_update` مؤثر با `IMMEDIATE`، سقفِ ورودِ ادمین، سقفِ
|
||||||
|
طولِ سؤالِ AI، مقایسهی زمانثابتِ `X-Internal-Key`.
|
||||||
|
|
||||||
|
هر تغییری در این مسیرها باید تستهای `payment/tests_audit_fixes.py` و
|
||||||
|
`accounts/tests_audit_fixes.py` را سبز نگه دارد.
|
||||||
Reference in New Issue
Block a user