harden OTP for real SMS and close the second audit round
Kavenegar is being switched on, so every OTP request now costs credit: - phone numbers are normalised to 09XXXXXXXXX everywhere (login, OTP, family invite, TEST_LOGIN_PHONES keys) and existing rows are migrated; non-Iranian and landline numbers are rejected before any SMS - cooldown and a daily per-phone cap are counted in the database, so the three gunicorn workers no longer triple them - a global daily OTP SMS cap (default 300) stops SMS pumping - per-IP rates widened for CGNAT / school Wi-Fi (env-overridable) - provider message id is logged; SMS text warns not to share the code Second audit round: an offer no longer shortens a longer paid plan, Zarinpal error bodies no longer 500, island XP reaches the parent report, a non-numeric questionId no longer 500s after the answer is generated. 596 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,7 +58,7 @@ class OtpTests(TestCase):
|
||||
APIClient().post('/api/auth/otp/request', {'phone': phone}, format='json')
|
||||
|
||||
def test_a_code_dies_after_five_wrong_guesses(self):
|
||||
phone = '+989121234001'
|
||||
phone = '09121234001'
|
||||
self._request(phone)
|
||||
for guess in ('000001', '000002', '000003', '000004', '000005'):
|
||||
APIClient().post('/api/auth/login', {'phone': phone, 'otp': guess}, format='json')
|
||||
@@ -68,7 +68,7 @@ class OtpTests(TestCase):
|
||||
|
||||
|
||||
@override_settings(DEBUG=True, OTP_REQUEST_PHONE_COOLDOWN_SECONDS=0,
|
||||
TEST_LOGIN_PHONES={'+989120000101': '4321'})
|
||||
TEST_LOGIN_PHONES={'09120000101': '4321'})
|
||||
class GuestSignupKeepsProgressTests(TestCase):
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
@@ -85,25 +85,25 @@ class GuestSignupKeepsProgressTests(TestCase):
|
||||
def test_the_guest_becomes_the_registered_user(self):
|
||||
with patch('accounts.views.secrets.randbelow', return_value=654321), \
|
||||
patch('accounts.views.send_otp_sms'):
|
||||
self.client.post('/api/auth/otp/request', {'phone': '+989121234002'}, format='json')
|
||||
body = self._login('+989121234002', '654321').json()
|
||||
self.client.post('/api/auth/otp/request', {'phone': '09121234002'}, format='json')
|
||||
body = self._login('09121234002', '654321').json()
|
||||
|
||||
self.assertEqual(int(body['userId']), self.guest_id)
|
||||
user = User.objects.get(pk=self.guest_id)
|
||||
self.assertEqual((user.phone, user.is_guest), ('+989121234002', False))
|
||||
self.assertEqual((user.phone, user.is_guest), ('09121234002', False))
|
||||
self.assertEqual(UserStats.objects.get(user_id=self.guest_id).xp, 120)
|
||||
|
||||
def test_an_existing_account_is_not_merged(self):
|
||||
existing = User.objects.create(phone='+989121234003', is_guest=False)
|
||||
existing = User.objects.create(phone='09121234003', is_guest=False)
|
||||
with patch('accounts.views.secrets.randbelow', return_value=111222), \
|
||||
patch('accounts.views.send_otp_sms'):
|
||||
self.client.post('/api/auth/otp/request', {'phone': '+989121234003'}, format='json')
|
||||
body = self._login('+989121234003', '111222').json()
|
||||
self.client.post('/api/auth/otp/request', {'phone': '09121234003'}, format='json')
|
||||
body = self._login('09121234003', '111222').json()
|
||||
self.assertEqual(int(body['userId']), existing.id)
|
||||
self.assertTrue(User.objects.get(pk=self.guest_id).is_guest)
|
||||
|
||||
def test_demo_test_phones_never_absorb_a_guest(self):
|
||||
body = self._login('+989120000101', '4321').json()
|
||||
body = self._login('09120000101', '4321').json()
|
||||
self.assertNotEqual(int(body['userId']), self.guest_id)
|
||||
self.assertTrue(User.objects.get(pk=self.guest_id).is_guest)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user