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:
@@ -76,15 +76,19 @@ def grant_pack(order) -> bool:
|
||||
plan = Plan.objects.filter(pk=order.plan_id, kind='pack').first()
|
||||
if plan is None:
|
||||
return False
|
||||
# عددها از خودِ سفارش (همان که فروخته شد)؛ سفارشِ بیاین ستونها (پیش از آنها، یا ساختهشده بیرون
|
||||
# از create_order) عددهای فعلیِ پلن را میگیرد، همان رفتارِ پیشین.
|
||||
credits = order.question_credits if order.question_credits is not None else plan.question_credits
|
||||
hearts_days = order.hearts_days if order.hearts_days is not None else plan.hearts_days
|
||||
try:
|
||||
with transaction.atomic():
|
||||
QuestionCredit.objects.create(user_id=order.user_id, delta=plan.question_credits,
|
||||
QuestionCredit.objects.create(user_id=order.user_id, delta=credits,
|
||||
reason='purchase', order_id=order.id, note=plan.title[:100])
|
||||
if plan.hearts_days:
|
||||
if hearts_days:
|
||||
now = timezone.now()
|
||||
hp = HeartsPass.objects.select_for_update().filter(user_id=order.user_id).first()
|
||||
start = hp.expires_at if hp and hp.expires_at > now else now
|
||||
until = start + timezone.timedelta(days=plan.hearts_days)
|
||||
until = start + timezone.timedelta(days=hearts_days)
|
||||
HeartsPass.objects.update_or_create(user_id=order.user_id, defaults={'expires_at': until})
|
||||
except IntegrityError:
|
||||
return False
|
||||
@@ -111,7 +115,7 @@ def ai_daily_quota(user_id: int) -> int:
|
||||
return max(quotas) if quotas else settings.AI_DAILY_QUOTA_FREE
|
||||
|
||||
|
||||
def activate_subscription(subscription_id: int, plan_id: int | None = None):
|
||||
def activate_subscription(subscription_id: int, plan_id: int | None = None, duration_days: int | None = None):
|
||||
"""
|
||||
بعد از تاییدِ پرداخت صدا زده میشود (کالبکِ زرینپال، تاییدِ بازار).
|
||||
|
||||
@@ -136,7 +140,9 @@ def activate_subscription(subscription_id: int, plan_id: int | None = None):
|
||||
# باید دیده شود. پلن را پاک نکنید؛ on_sale=False کنید.
|
||||
logger.error('activate_subscription: پلنِ %s برای اشتراکِ %s پیدا نشد', plan_id, subscription_id)
|
||||
plan = plan or subscription.plan
|
||||
duration_days = plan.duration_days if plan else 30
|
||||
# duration_days از سفارش (عددِ لحظهی خرید)؛ None برای سفارشِ پیش از آن ستون.
|
||||
if duration_days is None:
|
||||
duration_days = plan.duration_days if plan else 30
|
||||
now = timezone.now()
|
||||
still_running = (
|
||||
old_status == 'active' and subscription.expires_at is not None
|
||||
|
||||
Reference in New Issue
Block a user