throttling, abuse-protection & logging/monitoring pass
This commit is contained in:
@@ -5,6 +5,7 @@ from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from progress.models import UserHearts, UserStats
|
||||
@@ -23,6 +24,40 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
class GuestAwareContentThrottle(ScopedRateThrottle):
|
||||
"""
|
||||
⚠️ اضافه شده: همون مشکلِ GuestAwareAIThrottle (ai_tutor/views.py)،
|
||||
GuestAwareFamilyThrottle (family/views.py) و GuestAwareDiscountThrottle/
|
||||
GuestAwarePurchaseThrottle (discount/views.py، subscription/views.py)
|
||||
اینجا هم هست. ScopedRateThrottle استاندارد برای درخواستِ احرازهویتشده
|
||||
از request.user.pk بهعنوانِ کلیدِ throttle استفاده میکنه؛ چون
|
||||
GuestSessionView با هر deviceId جدید یک کاربرِ مهمانِ تازه (pk جدید)
|
||||
میسازه، یک مهاجم میتونست با ساختنِ پیدرپیِ حسابهای مهمان، برای هر
|
||||
حساب یک bucket تازه از throttle_scope 'question_answer'/'lesson_complete'
|
||||
بگیره و محدودیتِ واقعی رو دور بزنه — درحالیکه هر دو endpoint اثرِ
|
||||
جانبیِ ماندگار دارن (UserAnswer + کسرِ هارت، یا UserLessonCompletion +
|
||||
xp/coins/progress).
|
||||
|
||||
دقیقاً طبقِ همون الگو: فقط برای کاربرهای مهمان (is_guest=True) کلیدِ
|
||||
cache از pk کاربر به IP کلاینت (get_ident) تغییر میکنه، پس همهی
|
||||
حسابهای مهمانِ ساختهشده از یک IP یک bucket مشترک دارن و چرخشِ حساب
|
||||
دیگه bucket تازه نمیده. رفتارِ throttle برای کاربرهای غیرمهمان
|
||||
(ثبتنامشده) دقیقاً مثلِ قبل (کلید بر اساسِ pk) باقی میمونه؛ منطقِ
|
||||
answer/heart/progress/completion/idempotency دستنخوردهست.
|
||||
"""
|
||||
|
||||
def get_cache_key(self, request, view):
|
||||
user = request.user
|
||||
if user and user.is_authenticated and getattr(user, 'is_guest', False):
|
||||
ident = f'guest-ip:{self.get_ident(request)}'
|
||||
elif user and user.is_authenticated:
|
||||
ident = user.pk
|
||||
else:
|
||||
ident = self.get_ident(request)
|
||||
|
||||
return self.cache_format % {'scope': self.scope, 'ident': ident}
|
||||
|
||||
|
||||
class GradesListView(APIView):
|
||||
"""GET /api/grades — لیست پایهها"""
|
||||
|
||||
@@ -130,6 +165,20 @@ class QuestionAnswerView(APIView):
|
||||
|
||||
# userId همیشه از request.user.id گرفته میشود، نه ورودی کلاینت.
|
||||
permission_classes = [IsAuthenticated]
|
||||
# Idempotency-Key فقط جلوی retry با همون کلید رو میگیره، نه حجمِ بالای
|
||||
# درخواستهای *متفاوت* (بدون کلید یا با کلیدهای متفاوت) که هرکدوم یک
|
||||
# UserAnswer جدید میسازن و منطقِ هارت/NodeProgress رو اجرا میکنن —
|
||||
# حتی وقتی هارتِ کاربر صفر شده. ScopedRateThrottle وقتی request.user
|
||||
# احرازهویتشده باشه با pk کاربر کلید میخوره (نه فقط IP)، پس تغییرِ
|
||||
# IP این محدودیت رو دور نمیزنه.
|
||||
# ⚠️ اصلاح شده: چون کاربرهای مهمان (GuestSessionView) با هر deviceId
|
||||
# جدید یک pk تازه میگیرن، ScopedRateThrottleِ ساده (کلید بر اساسِ
|
||||
# user.pk) توسطِ چرخشِ حساب مهمان قابلدورزدن بود. حالا از
|
||||
# GuestAwareContentThrottleِ مشترک استفاده میشه که برای کاربرهای
|
||||
# مهمان کلید رو بر اساسِ IP میسازه و برای کاربرهای غیرمهمان رفتارِ
|
||||
# per-user قبلی رو دستنخورده نگه میداره.
|
||||
throttle_scope = 'question_answer'
|
||||
throttle_classes = [GuestAwareContentThrottle]
|
||||
|
||||
@extend_schema(
|
||||
request=AnswerRequestSerializer,
|
||||
@@ -229,6 +278,18 @@ class LessonCompleteView(APIView):
|
||||
# کلاینت اجازه میداد بهجای هر کاربر دیگری xp/coins ثبت کنه. حالا auth
|
||||
# اجباریه و همیشه از request.user.id استفاده میشه.
|
||||
permission_classes = [IsAuthenticated]
|
||||
# Idempotency-Key فقط جلوی retry با همون کلید رو میگیره، نه حجمِ بالای
|
||||
# درخواستهای *متفاوت* (بدون کلید یا با کلیدهای متفاوت) که هرکدوم یک
|
||||
# UserLessonCompletion جدید میسازن و سیگنالِ post_save (NodeProgress) +
|
||||
# بهروزرسانیِ UserStats رو اجرا میکنن. ScopedRateThrottle با pk
|
||||
# کاربرِ احرازهویتشده کلید میخوره، نه IP.
|
||||
# ⚠️ اصلاح شده: همون مشکلِ چرخشِ حسابِ مهمان (نگاه کنید به
|
||||
# GuestAwareContentThrottle بالا) اینجا هم بود؛ حالا از همون کلاسِ
|
||||
# مشترک استفاده میشه. نرخ هم از ۶۰/min به ۱۰/hour تغییر کرد چون این
|
||||
# endpoint رکوردِ completion ماندگار میسازه و xp/coins/progress رو
|
||||
# واقعاً تغییر میده (برخلافِ question_answer که فرکانسِ طبیعیاش بالاتره).
|
||||
throttle_scope = 'lesson_complete'
|
||||
throttle_classes = [GuestAwareContentThrottle]
|
||||
|
||||
@extend_schema(
|
||||
request=LessonCompleteRequestSerializer,
|
||||
|
||||
Reference in New Issue
Block a user