Remove the guest system entirely: no is_guest/device_id, no guest throttles, phone login is the only way in
accounts.0008 drops the columns and refuses to run while guest rows exist. Docs describe the current state; tests that only proved guest behaviour are gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,6 @@ from rest_framework.throttling import ScopedRateThrottle
|
||||
|
||||
from .authentication import issue_access_token
|
||||
from .models import OTPCode, Profile, Session, User, hash_refresh_token
|
||||
from .testing import guest_session
|
||||
from .services import OTPDeliveryError, SMSDeliveryError, send_otp_sms
|
||||
|
||||
|
||||
@@ -162,7 +161,6 @@ class SendOtpSmsTests(TestCase):
|
||||
self.assertIn('provider_status=401', message)
|
||||
|
||||
|
||||
|
||||
class RequestOTPViewTests(TestCase):
|
||||
"""
|
||||
تستهای سطح API برای RequestOTPView: مطمئن میشیم رفتارِ dev قبلی حفظ
|
||||
@@ -286,12 +284,11 @@ class RefreshTokenReplayTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(is_guest=True, device_id='device-refresh-test')
|
||||
self.user = User.objects.create(phone='09121230201')
|
||||
self.raw = 'test-refresh-token-abc123'
|
||||
self.session = Session.objects.create(
|
||||
user=self.user,
|
||||
refresh_token=hash_refresh_token(self.raw),
|
||||
device_id='device-refresh-test',
|
||||
expires_at=timezone.now() + timezone.timedelta(days=30),
|
||||
revoked=False,
|
||||
)
|
||||
@@ -760,14 +757,13 @@ class RefreshTokenThrottleTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(is_guest=True, device_id='device-refresh-throttle-test')
|
||||
self.user = User.objects.create(phone='09121230202')
|
||||
|
||||
def _create_session(self, token_suffix):
|
||||
raw = f'test-refresh-token-{token_suffix}'
|
||||
session = Session.objects.create(
|
||||
user=self.user,
|
||||
refresh_token=hash_refresh_token(raw),
|
||||
device_id='device-refresh-throttle-test',
|
||||
expires_at=timezone.now() + timezone.timedelta(days=30),
|
||||
revoked=False,
|
||||
)
|
||||
@@ -824,28 +820,6 @@ class RefreshTokenThrottleTests(TestCase):
|
||||
self.assertFalse(blocked_session.revoked)
|
||||
|
||||
|
||||
class GuestSessionThrottleTests(TestCase):
|
||||
"""
|
||||
🔒 اثبات محدودیتِ جدیدِ ScopedRateThrottle (scope='guest_session') روی
|
||||
GuestSessionView: درخواستهای تکراری از همون کلاینت (IP) باید بعد از
|
||||
حدِ مجاز با ۴۲۹ رد بشن.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
cache.clear()
|
||||
|
||||
def test_guest_login_endpoint_is_gone(self):
|
||||
# ورودِ مهمان حذف شده: هر کاربر باید با شماره ثبتنام کند. قدیمیها باید ۴۰۴
|
||||
# بگیرند، نه حسابِ مهمانِ ساختهشده.
|
||||
before = User.objects.count()
|
||||
response = self.client.post(
|
||||
'/api/auth/session', {'type': 'guest', 'deviceId': 'device-x'}, format='json',
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertEqual(User.objects.count(), before)
|
||||
|
||||
|
||||
class UserProfileViewTests(TestCase):
|
||||
"""تستهای GET /api/user/profile — پروفایل کاربر جاری."""
|
||||
|
||||
@@ -862,8 +836,8 @@ class UserProfileViewTests(TestCase):
|
||||
response = self._get_profile()
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_returns_profile_for_guest_without_existing_profile_row(self):
|
||||
user = User.objects.create(device_id='device-profile-1', is_guest=True, name='')
|
||||
def test_returns_profile_for_user_without_existing_profile_row(self):
|
||||
user = User.objects.create(phone='09000000055', name='')
|
||||
token = issue_access_token(user.id)
|
||||
|
||||
response = self._get_profile(token)
|
||||
@@ -871,8 +845,7 @@ class UserProfileViewTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json()
|
||||
self.assertEqual(data['userId'], str(user.id))
|
||||
self.assertEqual(data['isGuest'], True)
|
||||
self.assertIsNone(data['phone'])
|
||||
self.assertEqual(data['phone'], '09000000055')
|
||||
self.assertIsNone(data['gradeId'])
|
||||
self.assertIsNone(data['birthYear'])
|
||||
self.assertIsNone(data['birthDate'])
|
||||
@@ -882,7 +855,7 @@ class UserProfileViewTests(TestCase):
|
||||
|
||||
def test_returns_existing_profile_fields_for_registered_user(self):
|
||||
user = User.objects.create(
|
||||
phone='09120000001', is_guest=False, name='علی', avatar='avatar.png', grade_id=2,
|
||||
phone='09120000001', name='علی', avatar='avatar.png', grade_id=2,
|
||||
)
|
||||
Profile.objects.create(user=user, birth_year=1384, bio='سلام')
|
||||
token = issue_access_token(user.id)
|
||||
@@ -894,15 +867,14 @@ class UserProfileViewTests(TestCase):
|
||||
self.assertEqual(data['phone'], '09120000001')
|
||||
self.assertEqual(data['name'], 'علی')
|
||||
self.assertEqual(data['avatar'], 'avatar.png')
|
||||
self.assertEqual(data['isGuest'], False)
|
||||
self.assertEqual(data['gradeId'], 2)
|
||||
self.assertEqual(data['birthYear'], 1384)
|
||||
self.assertEqual(data['bio'], 'سلام')
|
||||
|
||||
def test_does_not_leak_other_users_profile(self):
|
||||
other = User.objects.create(device_id='device-other', is_guest=True, name='')
|
||||
other = User.objects.create(phone='09000000056', name='')
|
||||
Profile.objects.create(user=other, bio='محرمانه')
|
||||
user = User.objects.create(device_id='device-profile-2', is_guest=True, name='')
|
||||
user = User.objects.create(phone='09000000057', name='')
|
||||
token = issue_access_token(user.id)
|
||||
|
||||
response = self._get_profile(token)
|
||||
@@ -932,13 +904,13 @@ class SendSmsAdminActionTests(TestCase):
|
||||
# inactive/never-active موردنظرِ خودشون رو صریحاً میسازن.
|
||||
now = timezone.now()
|
||||
self.user1 = User.objects.create(
|
||||
name='u1', is_guest=False, phone='0910000001', last_activity_at=now,
|
||||
name='u1', phone='0910000001', last_activity_at=now,
|
||||
)
|
||||
self.user2 = User.objects.create(
|
||||
name='u2', is_guest=False, phone='0910000002', last_activity_at=now,
|
||||
name='u2', phone='0910000002', last_activity_at=now,
|
||||
)
|
||||
self.guest_no_phone = User.objects.create(
|
||||
name='guest', is_guest=True, phone=None, last_activity_at=now,
|
||||
self.no_phone_user = User.objects.create(
|
||||
name='no-phone', phone=None, last_activity_at=now,
|
||||
)
|
||||
self.changelist_url = reverse('admin:accounts_user_changelist')
|
||||
|
||||
@@ -963,10 +935,10 @@ class SendSmsAdminActionTests(TestCase):
|
||||
mock_send_sms.assert_not_called()
|
||||
|
||||
@patch('accounts.admin.send_sms')
|
||||
def test_apply_sends_sms_to_users_with_phone_and_skips_guest(self, mock_send_sms):
|
||||
def test_apply_sends_sms_to_users_with_phone_and_skips_users_without(self, mock_send_sms):
|
||||
response = self.client.post(self.changelist_url, {
|
||||
'action': 'send_sms_to_selected',
|
||||
'_selected_action': [self.user1.pk, self.user2.pk, self.guest_no_phone.pk],
|
||||
'_selected_action': [self.user1.pk, self.user2.pk, self.no_phone_user.pk],
|
||||
'apply': 'ارسال پیامک',
|
||||
'message': 'سلام از طرف ادمین',
|
||||
})
|
||||
@@ -1030,14 +1002,14 @@ class SendSmsAdminActionTests(TestCase):
|
||||
"""
|
||||
now = timezone.now()
|
||||
active_user = User.objects.create(
|
||||
name='active', is_guest=False, phone='0910000009', last_activity_at=now,
|
||||
name='active', phone='0910000009', last_activity_at=now,
|
||||
)
|
||||
inactive_user = User.objects.create(
|
||||
name='inactive', is_guest=False, phone='0910000010',
|
||||
name='inactive', phone='0910000010',
|
||||
last_activity_at=now - timezone.timedelta(days=90),
|
||||
)
|
||||
never_active_user = User.objects.create(
|
||||
name='never-active', is_guest=False, phone='0910000011', last_activity_at=None,
|
||||
name='never-active', phone='0910000011', last_activity_at=None,
|
||||
)
|
||||
cutoff_date = (now - timezone.timedelta(days=30)).date()
|
||||
|
||||
@@ -1085,7 +1057,7 @@ class SendSmsAdminActionTests(TestCase):
|
||||
'inactive' هم مثلِ 'selected' حفظ بشه.
|
||||
"""
|
||||
User.objects.create(
|
||||
name='inactive-no-phone', is_guest=True, phone=None,
|
||||
name='inactive-no-phone', phone=None,
|
||||
last_activity_at=timezone.now() - timezone.timedelta(days=90),
|
||||
)
|
||||
cutoff_date = (timezone.now() - timezone.timedelta(days=30)).date()
|
||||
@@ -1115,7 +1087,7 @@ class SendSmsAdminActionTests(TestCase):
|
||||
"""
|
||||
response = self.client.post(self.changelist_url, {
|
||||
'action': 'send_sms_to_selected',
|
||||
'_selected_action': [self.user1.pk, self.user2.pk, self.guest_no_phone.pk],
|
||||
'_selected_action': [self.user1.pk, self.user2.pk, self.no_phone_user.pk],
|
||||
'apply': 'ارسال پیامک',
|
||||
'message': 'متن',
|
||||
'target': 'selected',
|
||||
@@ -1137,11 +1109,11 @@ class SendSmsAdminActionTests(TestCase):
|
||||
"""
|
||||
now = timezone.now()
|
||||
User.objects.create(
|
||||
name='inactive-1', is_guest=False, phone='0910000021',
|
||||
name='inactive-1', phone='0910000021',
|
||||
last_activity_at=now - timezone.timedelta(days=90),
|
||||
)
|
||||
User.objects.create(
|
||||
name='inactive-2', is_guest=False, phone='0910000022',
|
||||
name='inactive-2', phone='0910000022',
|
||||
last_activity_at=now - timezone.timedelta(days=90),
|
||||
)
|
||||
cutoff_date = (now - timezone.timedelta(days=30)).date()
|
||||
@@ -1185,7 +1157,7 @@ class SendSmsAdminActionTests(TestCase):
|
||||
"""
|
||||
response = self.client.post(self.changelist_url, {
|
||||
'action': 'send_sms_to_selected',
|
||||
'_selected_action': [self.user1.pk, self.guest_no_phone.pk],
|
||||
'_selected_action': [self.user1.pk, self.no_phone_user.pk],
|
||||
'apply': 'ارسال پیامک',
|
||||
'message': 'متن',
|
||||
'target': 'selected',
|
||||
@@ -1226,7 +1198,7 @@ class SupervisorGroupAccessTests(TestCase):
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
self.target_user = User.objects.create(name='target', is_guest=False, phone='0910000099')
|
||||
self.target_user = User.objects.create(name='target', phone='0910000099')
|
||||
self.plan = Plan.objects.create(title='یکماهه', price=10000)
|
||||
self.order = Order.objects.create(
|
||||
user_id=self.target_user.id, amount=10000, idempotency_key='sup-order-1',
|
||||
@@ -1333,7 +1305,7 @@ class SupervisorGroupAccessTests(TestCase):
|
||||
original_name = self.target_user.name
|
||||
response = self.client.post(
|
||||
reverse('admin:accounts_user_change', args=[self.target_user.pk]),
|
||||
{'name': 'دستکاریشده', 'is_guest': 'on'},
|
||||
{'name': 'دستکاریشده'},
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.target_user.refresh_from_db()
|
||||
@@ -1422,7 +1394,7 @@ class AuthUserRowCacheTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='dev-authcache', is_guest=True, name='n')
|
||||
self.user = User.objects.create(phone='09000000058', name='n')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
# هر endpointِ authenticated؛ /api/user/hearts سبک است.
|
||||
self.url = '/api/user/hearts'
|
||||
@@ -1492,7 +1464,7 @@ class UpdateUserProfileTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
cache.clear()
|
||||
self.user = User.objects.create(device_id='device-patch-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000059', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
def tearDown(self):
|
||||
@@ -1536,13 +1508,12 @@ class UpdateUserProfileTests(TestCase):
|
||||
def test_empty_body_is_rejected(self):
|
||||
self.assertEqual(self._patch({}).status_code, 400)
|
||||
|
||||
def test_phone_and_guest_flag_cannot_be_changed(self):
|
||||
response = self._patch({'name': 'رضا', 'phone': '09120000000', 'isGuest': False})
|
||||
def test_phone_cannot_be_changed(self):
|
||||
response = self._patch({'name': 'رضا', 'phone': '09120000000'})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.user.refresh_from_db()
|
||||
self.assertIsNone(self.user.phone)
|
||||
self.assertTrue(self.user.is_guest)
|
||||
self.assertNotEqual(self.user.phone, '09120000000')
|
||||
|
||||
def test_out_of_range_birth_year_is_rejected(self):
|
||||
self.assertEqual(self._patch({'birthYear': 2026}).status_code, 400)
|
||||
@@ -1660,8 +1631,11 @@ class RefreshTokenStoredHashedTests(TestCase):
|
||||
|
||||
def test_issued_tokens_are_stored_only_as_hash(self):
|
||||
client = APIClient()
|
||||
body = guest_session('hash-dev')
|
||||
raw = body['refreshToken']
|
||||
user = User.objects.create(phone='09121230203')
|
||||
raw = 'raw-token-for-hash-test'
|
||||
Session.objects.create(user=user, refresh_token=hash_refresh_token(raw),
|
||||
expires_at=timezone.now() + timezone.timedelta(days=1))
|
||||
self.assertFalse(Session.objects.filter(refresh_token=raw).exists())
|
||||
self.assertFalse(Session.objects.filter(refresh_token=raw).exists())
|
||||
self.assertTrue(Session.objects.filter(refresh_token=hash_refresh_token(raw)).exists())
|
||||
|
||||
@@ -1674,7 +1648,7 @@ class RefreshTokenStoredHashedTests(TestCase):
|
||||
|
||||
from django.apps import apps as global_apps
|
||||
|
||||
user = User.objects.create(is_guest=True, device_id='hash-mig')
|
||||
user = User.objects.create(phone='09121230204')
|
||||
Session.objects.create(user=user, refresh_token='old-raw-token',
|
||||
expires_at=timezone.now() + timezone.timedelta(days=1))
|
||||
import_module('accounts.migrations.0007_hash_refresh_tokens').hash_existing(global_apps, None)
|
||||
|
||||
Reference in New Issue
Block a user