record the 2026-09-26 Bazaar fix deploy: bdd5aba live, authorize link sent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 22:01:39 +03:30
parent bdd5aba985
commit f6cdfddaa9
4 changed files with 71 additions and 11 deletions

View File

@@ -76,6 +76,27 @@ def send_sms(*, phone: str, message: str) -> None:
می‌زنه، و پاسخ رو اعتبارسنجی می‌کنه. کلیدِ API هیچ‌وقت داخل کد نیست؛
همیشه از env خونده می‌شه (settings.KAVENEGAR_API_KEY).
"""
payload = {'receptor': phone, 'message': message}
if settings.KAVENEGAR_SENDER:
payload['sender'] = settings.KAVENEGAR_SENDER
_kavenegar_post(phone=phone, method='sms/send.json', payload=payload)
def send_verify_lookup(*, phone: str, template: str, token: str) -> None:
"""
ارسال با الگوی تأییدشده‌ی کاوه‌نگار (verify/lookup) — متن را الگوی پنل می‌سازد
و ما فقط token را می‌فرستیم. کاوه‌نگارِ این حساب پیامکِ متنِ آزاد را از خطِ
پیش‌فرض لغو می‌کند (وضعیتِ ۱۳، ۲۰۲۶-۰۹-۲۵) و کدِ ورود را فقط از این راه می‌پذیرد.
token نباید فاصله داشته باشد (قاعده‌ی کاوه‌نگار)؛ کدِ OTP شش رقمِ پیوسته است.
"""
_kavenegar_post(
phone=phone, method='verify/lookup.json',
payload={'receptor': phone, 'template': template, 'token': token},
)
def _kavenegar_post(*, phone: str, method: str, payload: dict) -> None:
"""فراخوانیِ مشترکِ کاوه‌نگار و سنجشِ پاسخ؛ هر شکستی SMSDeliveryError است."""
if not settings.KAVENEGAR_API_KEY:
logger.error(
'SMS misconfigured: KAVENEGAR_API_KEY is not set (phone_suffix=%s)',
@@ -83,13 +104,9 @@ def send_sms(*, phone: str, message: str) -> None:
)
raise SMSDeliveryError('پیکربندیِ سرویسِ پیامک ناقص است.')
payload = {'receptor': phone, 'message': message}
if settings.KAVENEGAR_SENDER:
payload['sender'] = settings.KAVENEGAR_SENDER
try:
response = requests.post(
f'https://api.kavenegar.com/v1/{settings.KAVENEGAR_API_KEY}/sms/send.json',
f'https://api.kavenegar.com/v1/{settings.KAVENEGAR_API_KEY}/{method}',
data=payload,
timeout=5,
)
@@ -146,7 +163,8 @@ def send_otp_sms(*, phone: str, code: str) -> None:
واقعی‌ای نمی‌فرسته — چون در این محیط، RequestOTPView خودِ کد رو در
فیلدِ devOtp پاسخ برمی‌گردونه و SMS واقعی لازم نیست (رفتار قبلی dev
حفظ شده).
- DEBUG=False (production): send_sms صدا زده می‌شه؛ هر SMSDeliveryError
- DEBUG=False (production): اگر KAVENEGAR_OTP_TEMPLATE تنظیم باشد با الگو
(send_verify_lookup)، وگرنه متنِ آزادِ OTP_SMS_TEMPLATE با send_sms؛ هر SMSDeliveryError
به OTPDeliveryError نگاشت می‌شه تا رفتار/نوعِ استثنایی که
RequestOTPView انتظارش رو داره حفظ بمونه (کاربر پاسخ ۵۰۳ صریح
می‌گیره، نه یک success دروغین).
@@ -158,9 +176,11 @@ def send_otp_sms(*, phone: str, code: str) -> None:
)
return
message = settings.OTP_SMS_TEMPLATE.format(code=code)
try:
send_sms(phone=phone, message=message)
if settings.KAVENEGAR_OTP_TEMPLATE:
send_verify_lookup(phone=phone, template=settings.KAVENEGAR_OTP_TEMPLATE, token=code)
else:
send_sms(phone=phone, message=settings.OTP_SMS_TEMPLATE.format(code=code))
except SMSDeliveryError as exc:
raise OTPDeliveryError(str(exc)) from exc

View File

@@ -128,3 +128,39 @@ class ProviderLoggingTests(TestCase):
joined = '\n'.join(logs.output)
self.assertIn('messageid=8792343', joined)
self.assertNotIn('123456', joined)
def _kavenegar_reply(http=200, status=200):
return type('R', (), {'status_code': http, 'json': lambda self: {
'return': {'status': status}, 'entries': [{'messageid': 1, 'status': 1}] if status == 200 else None}})()
@override_settings(DEBUG=False, KAVENEGAR_API_KEY='k', TEST_LOGIN_PHONES={})
class VerifyLookupTests(TestCase):
"""خطِ حسابِ فعلی متنِ آزاد را لغو می‌کند (وضعیتِ ۱۳)؛ کدِ ورود باید با الگو برود."""
def setUp(self):
cache.clear()
@override_settings(KAVENEGAR_OTP_TEMPLATE='tadrisino-otp')
def test_otp_goes_through_the_template_with_only_the_code(self):
with patch('accounts.views.secrets.randbelow', return_value=4321), \
patch('accounts.services.requests.post', return_value=_kavenegar_reply()) as post:
self.assertEqual(_request('09121113301').status_code, 200)
url, data = post.call_args.args[0], post.call_args.kwargs['data']
self.assertTrue(url.endswith('/verify/lookup.json'))
self.assertEqual(data, {'receptor': '09121113301', 'template': 'tadrisino-otp', 'token': '004321'})
@override_settings(KAVENEGAR_OTP_TEMPLATE='')
def test_without_a_template_the_plain_send_is_kept(self):
with patch('accounts.services.requests.post', return_value=_kavenegar_reply()) as post:
self.assertEqual(_request('09121113302').status_code, 200)
self.assertTrue(post.call_args.args[0].endswith('/sms/send.json'))
self.assertIn('message', post.call_args.kwargs['data'])
@override_settings(KAVENEGAR_OTP_TEMPLATE='misspelled')
def test_unknown_template_is_an_explicit_503_and_frees_the_cooldown(self):
# کاوه‌نگار برای الگوی ناموجود/تأییدنشده HTTP ۴۲۴ می‌دهد (۲۰۲۶-۰۹-۲۵ زنده دیده شد).
with patch('accounts.services.requests.post', return_value=_kavenegar_reply(424, 424)):
self.assertEqual(_request('09121113303').status_code, 503)
self.assertFalse(OTPCode.objects.filter(phone='09121113303').exists())