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:
@@ -420,11 +420,11 @@ anyone who can read this repo can forge a token for any user.**
|
||||
|
||||
## وضعیت ۲۰۲۶-۰۹-۲۶ — اتصالِ API بازار
|
||||
|
||||
`8763ffd` روی سرور است (ریاستارت ۲۱:۳۳، migration `payment.0008`). Client ID/Secretِ بازار
|
||||
`bdd5aba` روی سرور است (ریاستارت ۲۲:۰۱، migration `payment.0008`؛ `/api/health` → `integrations.bazaar`). Client ID/Secretِ بازار
|
||||
در `.env` سرور است (بکاپِ پیشش: `.deploy-backups/env-before-bazaar-20260926-213108`).
|
||||
Redirect URIِ ثبتشده در پنلِ بازار خودِ `/api/payment/bazaar/verify` است: GET کدِ بازار را
|
||||
میگیرد، POST خریدِ اپ. لینکِ «اجازه» ۲۶ سپتامبر ۲۱:۳۳ ساخته شد و ۷۲ ساعت باز است؛ تا
|
||||
صاحبِ حسابِ بازار بازش نکند refresh token نداریم. **هنوز نداریم:** اسمِ پکیجِ اپ (کد حدسِ
|
||||
میگیرد، POST خریدِ اپ. لینکِ «اجازه» ۲۶ سپتامبر ۲۱:۳۳ ساخته شد، ۷۲ ساعت باز است و همان شب برای سنیور فرستاده شد؛
|
||||
تا صاحبِ حسابِ بازار تأیید نزند refresh token نداریم (`bazaar: false`). **هنوز نداریم:** اسمِ پکیجِ اپ (کد حدسِ
|
||||
`ir.tadrisino.app` را دارد)، SKUِ پلنها، و اپِ اندرویدیِ دارای Poolakey. آدرسِ سنجشِ خرید با
|
||||
سندِ فعلیِ بازار مقایسه شد و **هر دو آدرسِ سنجشِ خرید غلط بودند** (یعنی هیچ خریدِ واقعیای
|
||||
تأیید نمیشد)؛ درست شدند، بهعلاوهی روشِ جدیدِ بازار (توکنِ ثابتِ پیشخان در
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -674,6 +674,10 @@ TEST_LOGIN_PHONES = _parse_test_login_phones(os.environ.get('TEST_LOGIN_PHONES',
|
||||
# بدون تنظیم این env، ارسال باید صراحتاً خطا بدهد، نه silently موفق باشد.
|
||||
KAVENEGAR_API_KEY = os.environ.get('KAVENEGAR_API_KEY', '')
|
||||
KAVENEGAR_SENDER = os.environ.get('KAVENEGAR_SENDER', '')
|
||||
# نامِ الگوی تأییدشده در پنلِ کاوهنگار برای کدِ ورود (verify/lookup، با %token%).
|
||||
# وقتی تنظیم باشد OTP_SMS_TEMPLATE پایین استفاده نمیشود و متن را خودِ الگو میسازد.
|
||||
# خطِ پیشفرضِ حسابِ فعلی پیامکِ متنِ آزاد را لغو میکند، پس روی سرور لازم است.
|
||||
KAVENEGAR_OTP_TEMPLATE = os.environ.get('KAVENEGAR_OTP_TEMPLATE', '').strip()
|
||||
# جملهی دوم هشدارِ مهندسیِ اجتماعی است (رایج در پیامکهای بانکی)؛ کلِ متن زیرِ
|
||||
# ۷۰ نویسه میماند تا یک صفحه پیامکِ فارسی حساب شود، نه دو صفحه.
|
||||
OTP_SMS_TEMPLATE = os.environ.get(
|
||||
|
||||
Reference in New Issue
Block a user