Security review fixes: admin permissions, guest sessions, payment edge cases, assistant leaks

- Admin: refund/cancel/mark-resolved/Bazaar-link actions need change permission; question and
  curriculum import need add/delete; content browser needs view_question; guest device ids are
  shown to superusers only.
- Accounts: registering a guest's phone revokes the guest's earlier sessions; self-deletion keeps
  Redemption, UserOffer and the phone's OTP rows; the public demo guest cannot be deleted
  (DEMO_GUEST_DEVICE_IDS); display-name contact filter covers any separator and social handles.
- Payment: callback without Authority is 400; a second Bazaar purchase on a paid order is 409;
  orders snapshot credits/hearts/days at creation (payment.0011).
- Assistant: a broken diagram no longer makes a streamed answer free; a late off-topic marker
  refuses the answer (not stored, not cached); a disconnect saves the partial answer; only
  KNOWN_COLLECTIONS are searched.
- Infra: secret-key file is created atomically (workers no longer split keys); no default
  INTERNAL_SERVICE_KEY; Kavenegar key and Bazaar token kept out of error logs; Swagger/ReDoc
  pinned; schema throttled; docker compose bound to 127.0.0.1; non-ASCII digit ids are 400;
  ticket text capped at 5000.
- docs/security-before-production.md: remaining open items 24-31 and the guard tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 04:52:25 +03:30
parent 645baf846b
commit a973c41db2
36 changed files with 944 additions and 85 deletions

View File

@@ -27,8 +27,10 @@ services:
# idempotent است (مطابقِ همون workflow ای که در README برای اجرای
# محلی/غیرِ Docker هم توصیه شده).
command: sh -c "python manage.py migrate --noinput && python manage.py runserver 0.0.0.0:8000"
# فقط روی خودِ لپ‌تاپ: این پشته DEBUG=1 دارد (کدِ 0000 برای هر شماره) و Grafana بی‌رمز است؛
# روی 0.0.0.0 هر همسایه‌ی وای‌فای به آن‌ها می‌رسید.
ports:
- "8000:8000"
- "127.0.0.1:8000:8000"
environment:
# این مقادیر فقط برای اجرای محلیِ Docker/دِمو هستن؛ برای پروداکشن
# واقعی جایگزین بشن (به .env.example نگاه کنید).
@@ -53,7 +55,7 @@ services:
image: grafana/loki:2.9.6
container_name: tadrisino_loki
ports:
- "3100:3100"
- "127.0.0.1:3100:3100"
command: -config.file=/etc/loki/loki-config.yml
volumes:
- ./docker/loki/loki-config.yml:/etc/loki/loki-config.yml:ro
@@ -71,7 +73,7 @@ services:
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
ports:
- "12345:12345" # اختیاری: UI دیباگِ Alloy (وضعیتِ کامپوننت‌ها)
- "127.0.0.1:12345:12345" # اختیاری: UI دیباگِ Alloy (وضعیتِ کامپوننت‌ها)
volumes:
- ./docker/alloy/config.alloy:/etc/alloy/config.alloy:ro
- django_logs:/var/log/django:ro # همون لاگ‌های Django رو می‌خونه (read-only)
@@ -86,12 +88,12 @@ services:
image: grafana/grafana:10.4.3
container_name: tadrisino_grafana
ports:
- "3000:3000"
- "127.0.0.1:3000:3000"
environment:
# فقط برای دسترسیِ محلی/دِمو ساده شده (بدونِ لاگین)؛ برای هر چیزِ
# عمومی/پروداکشن حتماً anonymous access رو خاموش و پسورد ست کنید.
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
volumes:
- ./docker/grafana/provisioning:/etc/grafana/provisioning:ro