- accounts admin: phone/is_guest read-only for non-superusers, device_id filter lookups refused, auth.User/Group admin writable only by superusers - campaigns: delivery log needs view permission - phone/OTP/birth date: any Unicode digit normalised or rejected with 400; unknown gradeId is 400 - shared demo guest is never turned into a person's account by logging in - /api/home with unknown gradeId is no longer cached (LocMemCache flood reset throttle counters) - /api/progress/nodes: one query, huge unit is 400 - BazaarCredential token filter lookups refused Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
44 lines
2.3 KiB
Python
44 lines
2.3 KiB
Python
"""
|
||
نرمالسازیِ شمارهی موبایلِ ایران به شکلِ یکتای 09XXXXXXXXX.
|
||
|
||
چرا: پیش از این `phone` رشتهی خام بود و 0912…، +98912…، 98912… و همان
|
||
شماره با ارقامِ فارسی چهار «شماره»ی جدا بودند — هر کدام cooldown و سقفِ پیامکِ
|
||
خودش را داشت و حسابِ جدا میساخت (security-before-production.md، بندِ ۱۷).
|
||
هرچه شکلِ 09\\d{9} نگیرد رد میشود، پس شمارهی خارجی یا خطِ ثابت هم به
|
||
کاوهنگار نمیرسد (پیامکِ بینالمللی گران است و هدفِ SMS pumping).
|
||
|
||
عمداً بیوابستگی به DRF است تا config/settings.py هم بتواند واردش کند
|
||
(کلیدهای TEST_LOGIN_PHONES)؛ فیلدِ serializer در accounts/serializers.py است.
|
||
"""
|
||
import re
|
||
import unicodedata
|
||
|
||
_SEPARATORS = re.compile(r'[\s\-().\u200c\u200e\u200f]')
|
||
# [0-9] نه \d: در پایتون \d هر رقمِ یونیکد (تمامعرض، ریاضی، دوناگری…) را هم میگیرد.
|
||
_MOBILE = re.compile(r'09[0-9]{9}')
|
||
|
||
|
||
def ascii_digits(value: str) -> str:
|
||
"""
|
||
هر رقمِ دهدهیِ یونیکد (فارسی، عربی، تمامعرض «0»، ریاضی «𝟎»، …) → رقمِ لاتین.
|
||
|
||
بدونِ این «09123456789» شمارهی جدایی میشد با سقفِ پیامک و حسابِ جدا. «²» و «①» رقمِ
|
||
دهدهی نیستند و دست نمیخورند، پس شمارهای که آنها را دارد رد میشود.
|
||
"""
|
||
return ''.join(str(unicodedata.decimal(ch)) if not ch.isascii() and ch.isdecimal() else ch
|
||
for ch in value)
|
||
|
||
|
||
def normalize_phone(value) -> str | None:
|
||
"""شکلِ یکتای 09XXXXXXXXX، یا None اگر موبایلِ ایران نباشد."""
|
||
s = _SEPARATORS.sub('', ascii_digits(str(value or '')))
|
||
if s.startswith('+98'):
|
||
s = '0' + s[3:]
|
||
elif s.startswith('0098'):
|
||
s = '0' + s[4:]
|
||
elif s.startswith('98') and len(s) == 12:
|
||
s = '0' + s[2:]
|
||
elif s.startswith('9') and len(s) == 10:
|
||
s = '0' + s
|
||
return s if _MOBILE.fullmatch(s) else None
|