Signed payment links, hashed refresh tokens, login-only leaderboard, load test tools
- /api/payment/orders/{id}/pay requires the short-lived signature that paymentUrl carries;
without it (or with another order's) it is the same 404 as an unknown order
- Session.refresh_token stores the SHA-256 of the token; migration accounts.0007 hashes the
existing rows so tokens clients already hold keep working
- /api/leaderboard requires a token (guests have one); names of children were scrapable
- RAG mock only for `manage.py test`, not any command with an argument named "test"
- tools/loadtest: user factory, load generator, fake GapGPT+Qdrant, with method notes
- security-before-production.md lists only open items; closed ones become a guards table
with the tests that hold them. Comments describe current behaviour, not history
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ from urllib.parse import urlsplit
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.utils.html import escape
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
@@ -25,7 +25,7 @@ from .serializers import (
|
||||
OrderDetailResponseSerializer,
|
||||
ZarinpalCallbackResponseSerializer,
|
||||
)
|
||||
from .services import apply_order_payment_result, create_order, reconcile_pending_order
|
||||
from .services import apply_order_payment_result, create_order, pay_link_is_valid, reconcile_pending_order
|
||||
|
||||
|
||||
def _redirect_to_configured_url(url: str) -> HttpResponseRedirect:
|
||||
@@ -142,10 +142,10 @@ class PaymentRedirectView(APIView):
|
||||
(مثلاً دکمهی back مرورگر) همان authority قبلی را دوباره استفاده
|
||||
میکند، نه یک PaymentRequest تازه.
|
||||
|
||||
⚠️ AllowAny است و مالکیتِ سفارش را نمیسنجد (مرورگر هدرِ Bearer نمیفرستد)؛
|
||||
شناسهی سفارش ترتیبی است. هر کسی با باز کردنِ `/orders/<n>/pay` برای سفارشِ
|
||||
دیگران authority میسازد و از آن به بعد صاحبِ سفارش دیگر نمیتواند کدِ
|
||||
تخفیف بزند (بازبینیِ ۲۰۲۶-۰۹-۲۵، بازتولید شد).
|
||||
AllowAny است چون مرورگر هدرِ Bearer نمیفرستد؛ بهجایش پارامترِ `t`، امضای
|
||||
کوتاهعمری که فقط در paymentUrlِ صاحبِ سفارش میآید (payment/services.py،
|
||||
pay_link_token)، سنجیده میشود. بیامضا یا امضای سفارشِ دیگر همان ۴۰۴ِ سفارشِ
|
||||
ناموجود را میگیرد تا وجودِ شناسه هم لو نرود.
|
||||
"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
@@ -164,11 +164,17 @@ class PaymentRedirectView(APIView):
|
||||
return response
|
||||
|
||||
@extend_schema(
|
||||
parameters=[OpenApiParameter(
|
||||
't', OpenApiTypes.STR, required=True,
|
||||
description='امضای همراهِ paymentUrl؛ کلاینت خودش نمیسازد، همان آدرسِ کامل را باز میکند.',
|
||||
)],
|
||||
responses={302: OpenApiTypes.NONE},
|
||||
description='به درگاهِ زرینپال ریدایرکت میکند؛ بدنهی JSON ندارد.',
|
||||
tags=['Payment (payment)'],
|
||||
)
|
||||
def get(self, request, order_id):
|
||||
if not pay_link_is_valid(order_id, request.query_params.get('t', '')):
|
||||
raise Http404
|
||||
order = get_object_or_404(Order, pk=order_id, provider='gateway')
|
||||
|
||||
if order.status != 'pending':
|
||||
|
||||
Reference in New Issue
Block a user