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:
@@ -49,8 +49,27 @@ class SendSMSForm(forms.Form):
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class DeviceIdForSuperuserOnly:
|
||||
"""
|
||||
شناسهی دستگاه برای مهمان تنها رمزِ ورود است (`POST /api/auth/session`)؛ پس فقط ابرکاربر
|
||||
آن را در فهرست، جستجو و فرم میبیند، نه کارمندی با اجازهی دیدن (گروهِ Supervisor).
|
||||
"""
|
||||
|
||||
def get_list_display(self, request):
|
||||
cols = super().get_list_display(request)
|
||||
return cols if request.user.is_superuser else tuple(c for c in cols if c != 'device_id')
|
||||
|
||||
def get_search_fields(self, request):
|
||||
cols = super().get_search_fields(request)
|
||||
return cols if request.user.is_superuser else tuple(c for c in cols if c != 'device_id')
|
||||
|
||||
def get_exclude(self, request, obj=None):
|
||||
exclude = tuple(super().get_exclude(request, obj) or ())
|
||||
return exclude if request.user.is_superuser else exclude + ('device_id',)
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
class UserAdmin(DeviceIdForSuperuserOnly, admin.ModelAdmin):
|
||||
list_display = ('id', 'public_id', 'name', 'phone', 'device_id', 'is_guest', 'grade_id', 'created_at')
|
||||
search_fields = ('name', 'phone', 'device_id')
|
||||
|
||||
@@ -165,7 +184,7 @@ class ProfileAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Session)
|
||||
class SessionAdmin(admin.ModelAdmin):
|
||||
class SessionAdmin(DeviceIdForSuperuserOnly, admin.ModelAdmin):
|
||||
list_display = ('id', 'user', 'device_id', 'expires_at', 'revoked')
|
||||
list_filter = ('revoked',)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user