throttling, abuse-protection & logging/monitoring pass
This commit is contained in:
@@ -5,6 +5,7 @@ from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
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 payment.models import Order
|
||||
@@ -19,6 +20,41 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
class GuestAwarePurchaseThrottle(ScopedRateThrottle):
|
||||
"""
|
||||
⚠️ اضافه شده: همون مشکلِ GuestAwareAIThrottle (ai_tutor/views.py)،
|
||||
GuestAwareFamilyThrottle (family/views.py) و GuestAwareDiscountThrottle
|
||||
(discount/views.py) اینجا هم هست. ScopedRateThrottle استاندارد برای
|
||||
درخواستِ احرازهویتشده از request.user.pk بهعنوانِ کلیدِ throttle
|
||||
استفاده میکنه؛ چون GuestSessionView با هر deviceId جدید یک کاربرِ
|
||||
مهمانِ تازه (pk جدید) میسازه، یک مهاجم میتونست با ساختنِ پیدرپیِ
|
||||
حسابهای مهمان (تا سقفِ guest_session=20/hour)، برای هر حساب یک
|
||||
bucket تازه از throttle_scope 'purchase' بگیره — یعنی سقفِ ۱۰/ساعتِ
|
||||
درخواستِ purchase عملاً با چرخشِ حساب مهمان چند برابر میشد. این
|
||||
endpoint حساسِ مالیه: هر درخواستِ پذیرفتهشده وارد جریانِ purchase
|
||||
میشه، یک Subscription میسازه/آپدیت میکنه و create_order() رو صدا
|
||||
میزنه (که در پروداکشن با درگاهِ واقعی جایگزینِ stub فعلی میشه).
|
||||
|
||||
دقیقاً طبقِ همون الگو: فقط برای کاربرهای مهمان (is_guest=True) کلیدِ
|
||||
cache از pk کاربر به IP کلاینت (get_ident) تغییر میکنه، پس همهی
|
||||
حسابهای مهمانِ ساختهشده از یک IP یک bucket مشترک دارن و چرخشِ حساب
|
||||
دیگه bucket تازه نمیده. رفتارِ throttle برای کاربرهای غیرمهمان
|
||||
(ثبتنامشده) دقیقاً مثلِ قبل (کلید بر اساسِ pk) باقی میمونه؛ منطقِ
|
||||
purchase/subscription/order/Idempotency-Key/plan-validation دستنخوردهست.
|
||||
"""
|
||||
|
||||
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 PlansListView(APIView):
|
||||
"""GET /api/subscription/plans — list available plans."""
|
||||
|
||||
@@ -49,6 +85,22 @@ class PurchaseView(APIView):
|
||||
to create an Order."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
# scope کیدهای cache رو با pk کاربرِ احرازهویتشده میسازه (نه فقط IP)؛
|
||||
# چون Idempotency-Key بهتنهایی جلوی حجمِ بالای درخواستهای *متفاوت*
|
||||
# (با کلیدهای idempotency متفاوت) رو نمیگیره، این throttle مستقل و
|
||||
# مکملِ اون محافظته. ScopedRateThrottle خودش وقتی request.user
|
||||
# احرازهویتشده باشه (is_authenticated=True) بهجای IP از
|
||||
# request.user.pk بهعنوانِ ident استفاده میکنه، پس تغییرِ IP از
|
||||
# همون اکانت این محدودیت رو دور نمیزنه.
|
||||
# ⚠️ اصلاح شده: چون کاربرهای مهمان (GuestSessionView) با هر deviceId
|
||||
# جدید یک pk تازه میگیرن، ScopedRateThrottleِ ساده (کلید بر اساسِ
|
||||
# user.pk) توسطِ چرخشِ حساب مهمان قابلدورزدن بود. حالا از
|
||||
# GuestAwarePurchaseThrottle استفاده میشه که برای کاربرهای مهمان
|
||||
# کلید رو بر اساسِ IP میسازه (دقیقاً همون الگویِ GuestAwareAIThrottle،
|
||||
# GuestAwareFamilyThrottle و GuestAwareDiscountThrottle) و برای
|
||||
# کاربرهای غیرمهمان رفتارِ per-user قبلی رو دستنخورده نگه میداره.
|
||||
throttle_scope = 'purchase'
|
||||
throttle_classes = [GuestAwarePurchaseThrottle]
|
||||
|
||||
@extend_schema(
|
||||
request=PurchaseRequestSerializer,
|
||||
|
||||
Reference in New Issue
Block a user