throttling, abuse-protection & logging/monitoring pass
This commit is contained in:
@@ -9,6 +9,7 @@ from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework.exceptions import APIException, Throttled
|
||||
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 accounts.models import User as AccountUser
|
||||
@@ -26,6 +27,39 @@ logger = logging.getLogger(__name__)
|
||||
DAILY_AI_QUOTA = 15
|
||||
|
||||
|
||||
class GuestAwareAIThrottle(ScopedRateThrottle):
|
||||
"""
|
||||
⚠️ اضافه شده: ScopedRateThrottle استاندارد برای کاربرِ احرازهویتشده
|
||||
(مهمان یا نه) از request.user.pk بهعنوانِ کلیدِ throttle استفاده
|
||||
میکنه. چون GuestSessionView با هر deviceId جدید یک کاربرِ مهمانِ
|
||||
تازه (pk جدید) میسازه، یک مهاجم میتونست با ساختنِ پیدرپیِ حسابهای
|
||||
مهمان، برای هر حساب یک bucket مستقلِ throttle (و همینطور کوتای
|
||||
روزانهی AskAIView که هم بر اساسِ user_id شمارش میشه) بگیره — یعنی
|
||||
نرخِ واقعیِ استفاده از این endpoint گرونقیمت بهجایِ محدود به نرخِ
|
||||
ai_ask در ساعت، عملاً فقط به نرخِ ساختِ حساب مهمان (guest_session)
|
||||
محدود میشد.
|
||||
|
||||
این کلاس فقط برای کاربرهای مهمان (is_guest=True) کلیدِ cache رو از
|
||||
pk کاربر به IP کلاینت (همون get_ident که guest_session هم استفاده
|
||||
میکنه) تغییر میده؛ یعنی همهی حسابهای مهمانِ ساختهشده از یک IP
|
||||
مشترکِ throttle bucket ai_ask رو دارن و دیگه با عوضکردنِ حساب
|
||||
نمیشه bucket تازه گرفت. رفتارِ throttle برای کاربرهای غیرمهمان
|
||||
(ثبتنامشده با شماره) دقیقاً مثلِ قبل (کلید بر اساسِ pk) باقی
|
||||
میمونه؛ منطقِ DAILY_AI_QUOTA و بقیهی view دستنخوردهست.
|
||||
"""
|
||||
|
||||
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}
|
||||
|
||||
|
||||
def _is_db_ref_id(value) -> bool:
|
||||
"""
|
||||
⚠️ افزوده شده: lessonId/chapterId/questionId در قراردادِ API بهصورتِ
|
||||
@@ -105,6 +139,13 @@ class AskAIView(APIView):
|
||||
"""POST /api/ai/ask — سوال از AI (استریم اختیاری؛ در این فاز استریم پیاده نشده)"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
# ⚠️ افزوده شده: قبلاً این endpoint هیچ throttle مخصوصی نداشت؛ DAILY_AI_QUOTA
|
||||
# فقط سقفِ روزانه است و جلوی burst کوتاهمدت (مثلاً چند ده درخواست در چند
|
||||
# ثانیه به generate_tutor_answer) را نمیگیرد. throttle_scope مجزا
|
||||
# (ai_ask) این burst را محدود میکند، بدون اینکه به کوتای روزانه/idempotency
|
||||
# یا منطق AI دست بزند.
|
||||
throttle_scope = 'ai_ask'
|
||||
throttle_classes = [GuestAwareAIThrottle]
|
||||
|
||||
@extend_schema(
|
||||
request=AskRequestSerializer,
|
||||
|
||||
Reference in New Issue
Block a user