42 KiB
Executable file
طرح ادغام پیامک بهین اس ام اس (Behin SMS Integration Plan)
خلاصه
این مستند راهنمای کامل برای ادغام سرویس پیامک بهین اس ام اس (Behin SMS) در سیستم Hesabix است. بر اساس راهنمای ارائه شده، این طرح نحوه پیادهسازی و استفاده از API بهین اس ام اس را شرح میدهد.
موارد استفاده از پیامک در این طرح:
- ارسال ناتیفیکیشن به کاربران سیستم - از طریق سیستم Notification موجود
- ارسال پیامک به مشتریان و طرفهای حساب - برای اطلاعرسانی و ارتباط
- تایید شماره موبایل - در زمان ثبتنام یا تغییر شماره موبایل
- بازیابی کلمه عبور - از طریق ارسال OTP به موبایل
- ورود با OTP - امکان ورود بدون نیاز به رمز عبور
- احراز هویت دو مرحلهای (2FA) - افزایش امنیت حساب کاربری
ساختار فعلی سیستم
1. ساختار SMS Provider
سیستم فعلی یک SmsProvider پایه دارد که در مسیر زیر قرار دارد:
- Backend:
hesabixAPI/app/services/providers/sms_provider.py - این Provider فعلاً یک stub است و فقط
send_text()را به صورت placeholder پیادهسازی کرده است.
2. ساختار تنظیمات سیستم
تنظیمات SMS در بخش زیر ذخیره میشوند:
-
کلیدهای تنظیمات (در
system_settings_service.py):NOTIFY_SMS_PROVIDER= "sms_provider_name"NOTIFY_SMS_API_KEY= "sms_api_key"NOTIFY_SMS_SENDER= "sms_sender"
-
API Endpoint برای مدیر سیستم:
GET /api/v1/admin/system-settings/notifications- دریافت تنظیماتPUT /api/v1/admin/system-settings/notifications- ذخیره تنظیمات
-
UI صفحه تنظیمات:
hesabixUI/hesabix_ui/lib/pages/profile/notifications_settings_page.dart- در بخش Advanced، فیلدهای SMS Provider، API Key و Sender موجود است.
3. ساختار اطلاعات تماس
اطلاعات تماس مشتریان و طرفهای حساب در مدل Person ذخیره میشود:
- مدل:
hesabixAPI/adapters/db/models/person.py - فیلدهای مرتبط:
mobile: Mapped[str | None]- شماره موبایلphone: Mapped[str | None]- تلفن ثابتemail: Mapped[str | None]- ایمیل
نیازمندیهای ادغام
1. تنظیمات اضافی برای بهین اس ام اس
بر اساس راهنما، بهین اس ام اس نیاز به پارامترهای زیر دارد:
- UserName (نام کاربری)
- Password (کلمه عبور)
- SpecialNumber (شماره اختصاصی - همان
sms_senderاست)
پیشنهاد:
- میتوانیم
sms_api_keyرا برای ذخیره UserName و Password استفاده کنیم (به صورت JSON یا format خاص) - یا فیلدهای جداگانه اضافه کنیم:
sms_provider_usernamesms_provider_passwordsms_sender(قبلاً موجود است)
توصیه: بهتر است فیلدهای جداگانه اضافه شود تا امنیت و خوانایی بهتر شود.
2. تنظیمات اختیاری
- IsFlashMessage: آیا پیامک به صورت Flash ارسال شود (پیشفرض: False)
- CheckingMessageID: شناسه منحصر به فرد برای ردیابی (اختیاری)
طرح پیادهسازی
Phase 1: تکمیل تنظیمات سیستم
1.1 بهروزرسانی Backend Settings
فایل: hesabixAPI/app/services/system_settings_service.py
تغییرات مورد نیاز:
-
افزودن کلیدهای جدید:
NOTIFY_SMS_PROVIDER_USERNAME = "sms_provider_username" NOTIFY_SMS_PROVIDER_PASSWORD = "sms_provider_password" NOTIFY_SMS_IS_FLASH = "sms_is_flash" # اختیاری -
بهروزرسانی
get_notifications_settings()برای خواندن فیلدهای جدید -
بهروزرسانی
set_notifications_settings()برای ذخیره فیلدهای جدید -
بهروزرسانی
get_effective_notifications_settings()برای merge با env variables
1.2 بهروزرسانی API Endpoints
فایل: hesabixAPI/adapters/api/v1/admin/system_settings.py
تغییرات مورد نیاز:
-
بهروزرسانی
NotificationsConfigPayloadبرای شامل کردن:sms_provider_username: str | Nonesms_provider_password: str | Nonesms_is_flash: bool | None(اختیاری)
-
بهروزرسانی
put_notifications_settings_endpoint()برای پاس دادن فیلدهای جدید
1.3 بهروزرسانی UI تنظیمات
فایل: hesabixUI/hesabix_ui/lib/pages/profile/notifications_settings_page.dart
تغییرات مورد نیاز:
-
افزودن
TextEditingControllerبرای:_smsUsernameCtrl_smsPasswordCtrl(باobscureText: true)
-
افزودن
SwitchListTileبرای Flash Message (اختیاری) -
بهروزرسانی
_collectAdvancedPayload()برای شامل کردن فیلدهای جدید -
بهروزرسانی
_load()برای خواندن فیلدهای جدید
Phase 2: پیادهسازی Behin SMS Provider
2.1 ایجاد کلاس BehinSmsProvider
فایل جدید: hesabixAPI/app/services/providers/behin_sms_provider.py
ساختار کلی:
class BehinSmsProvider:
BASE_URL = "https://panel.behinsms.com/smsws/HttpService.ashx"
def __init__(self, username: str, password: str, sender: str):
self.username = username
self.password = password
self.sender = sender
def send_text(self, to_phone: str, text: str,
is_flash: bool = False,
checking_message_id: str | None = None) -> tuple[bool, str | None]:
"""
ارسال پیامک به یک یا چند شماره
Returns: (success: bool, message_id: str | None or error_code: str)
"""
# استفاده از متد SendArray
pass
def send_bulk(self, recipient_numbers: list[str], text: str,
is_flash: bool = False) -> tuple[bool, list[str] | None]:
"""
ارسال پیامک به چند شماره (تا 1000 شماره)
"""
pass
def get_credit(self) -> tuple[bool, float | None, str | None]:
"""
دریافت اعتبار باقیمانده
Returns: (success, credit_amount, error_message)
"""
pass
def get_message_status(self, message_id: str) -> tuple[bool, int | None, str | None]:
"""
دریافت وضعیت پیامک
Returns: (success, status_code, error_message)
"""
pass
متدهای پیادهسازی شده بر اساس راهنما:
- SendArray: برای ارسال عادی
- GetCredit: برای بررسی اعتبار
- GetMessageStatus: برای ردیابی وضعیت (اختیاری)
متدهای پیشرفته (برای آینده):
- SendArraySchedule: برای زمانبندی
- GetInboxMessage: برای دریافت پیامکهای ورودی
- SendNumberGroup: برای ارسال به گروهها
2.2 بهروزرسانی SmsProvider اصلی
فایل: hesabixAPI/app/services/providers/sms_provider.py
تغییرات:
class SmsProvider:
def __init__(self, *, provider_name: str | None = None,
api_key: str | None = None,
sender: str | None = None,
username: str | None = None,
password: str | None = None,
is_flash: bool = False):
# ...
# تشخیص Provider و استفاده از کلاس مناسب
if provider_name == "behinsms":
self._provider = BehinSmsProvider(
username=username or "",
password=password or "",
sender=sender or ""
)
else:
self._provider = None
def send_text(self, *, to_phone: str, text: str) -> bool:
if not self.is_configured():
return False
if self._provider:
success, _ = self._provider.send_text(to_phone, text, is_flash=self.is_flash)
return success
return False
2.3 بهروزرسانی NotificationService
فایل: hesabixAPI/app/services/notification_service.py
تغییرات:
notify_cfg = get_effective_notifications_settings(db)
self.sms = SmsProvider(
provider_name=notify_cfg.get("sms_provider_name"),
api_key=notify_cfg.get("sms_api_key"), # ممکن است استفاده نشود
sender=notify_cfg.get("sms_sender"),
username=notify_cfg.get("sms_provider_username"),
password=notify_cfg.get("sms_provider_password"),
is_flash=notify_cfg.get("sms_is_flash", False),
)
Phase 3: فرمتسازی شماره تلفن
بر اساس راهنما، شمارهها باید به فرمتهای زیر باشد:
0912???????(یازده کاراکتر) - پیشنهادی98912???????(دوازده کاراکتر)912???????(ده کاراکتر)
نیاز به Utility Function:
def normalize_phone_number(phone: str) -> str:
"""
نرمالسازی شماره تلفن به فرمت استاندارد بهین اس ام اس
"""
# حذف فاصله، خط تیره و ...
phone = re.sub(r'[\s\-\(\)]', '', phone)
# حذف + و 00
if phone.startswith('+98'):
phone = '0' + phone[3:]
elif phone.startswith('0098'):
phone = '0' + phone[4:]
elif phone.startswith('98'):
phone = '0' + phone[2:]
# اطمینان از شروع با 0
if not phone.startswith('0'):
phone = '0' + phone
# بررسی طول (باید 11 رقم باشد)
if len(phone) == 11 and phone.startswith('09'):
return phone
raise ValueError(f"فرمت شماره نامعتبر: {phone}")
Phase 4: مدیریت خطاها
بر اساس راهنما، کدهای خطا عددی هستند (کمتر از 1000 یا بزرگتر از 50).
نیاز به Error Mapping:
BEHINSMS_ERROR_CODES = {
51: "نام کاربری یا رمز عبور اشتباه است",
52: "نام کاربری یا رمز عبور خالی است",
54: "کلید RecipientNumber خالی است",
61: "شماره اختصاصی نامعتبر است",
63: "این IP اجازه دسترسی ندارد",
70: "کاربر غیر فعال شده است",
203: "به علت کمبود اعتبار پیام کوتاه شما توانایی ارسال ندارید",
# ...
}
Phase 5: ارسال پیامک به مشتریان و طرفهای حساب
5.1 ایجاد API Endpoint جدید
فایل: hesabixAPI/adapters/api/v1/business/persons.py (یا فایل مشابه)
Endpoint پیشنهادی:
@router.post("/persons/{person_id}/send-sms")
def send_sms_to_person(
person_id: int,
payload: SendSmsPayload,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""
ارسال پیامک به یک شخص (مشتری/طرف حساب)
"""
# 1. بررسی دسترسی کاربر به business
# 2. دریافت Person از دیتابیس
# 3. بررسی وجود شماره موبایل
# 4. نرمالسازی شماره
# 5. ارسال پیامک از طریق SmsProvider
# 6. ثبت در لاگ/تاریخچه
pass
Payload:
class SendSmsPayload(BaseModel):
message: str
is_flash: bool = False
5.2 ایجاد UI برای ارسال پیامک
مکانهای پیشنهادی:
-
صفحه جزئیات Person:
- دکمه "ارسال پیامک" در کنار شماره موبایل
- Dialog برای نوشتن پیام
-
صفحه لیست Persons:
- Action menu برای هر ردیف
- امکان انتخاب چند Person و ارسال گروهی
-
صفحه فاکتور (Invoice):
- دکمه "ارسال پیامک به مشتری" پس از ثبت فاکتور
- قالب پیشفرض پیامک (مثلاً "فاکتور شماره {invoice_number} به مبلغ {amount} ثبت شد")
5.3 ایجاد Service Layer
فایل جدید: hesabixAPI/app/services/person_sms_service.py
class PersonSmsService:
def __init__(self, db: Session):
self.db = db
# دریافت تنظیمات SMS
notify_cfg = get_effective_notifications_settings(db)
self.sms_provider = SmsProvider(...)
def send_to_person(self, person_id: int, message: str,
is_flash: bool = False,
business_id: int | None = None) -> dict:
"""
ارسال پیامک به یک Person
"""
# 1. دریافت Person
# 2. بررسی شماره موبایل
# 3. نرمالسازی
# 4. ارسال
# 5. ثبت در تاریخچه (اختیاری)
pass
def send_to_multiple(self, person_ids: list[int], message: str,
is_flash: bool = False) -> dict:
"""
ارسال به چند Person (تا 1000)
"""
pass
Phase 6: ثبت تاریخچه و لاگ
6.1 ایجاد مدل برای تاریخچه ارسال پیامک
فایل جدید: hesabixAPI/adapters/db/models/person_sms_history.py
class PersonSmsHistory(Base):
__tablename__ = "person_sms_history"
id: Mapped[int]
business_id: Mapped[int]
person_id: Mapped[int]
sent_by_user_id: Mapped[int]
recipient_number: Mapped[str]
message_text: Mapped[str]
message_id: Mapped[str | None] # از بهین اس ام اس
status: Mapped[str] # pending, sent, failed
is_flash: Mapped[bool]
error_message: Mapped[str | None]
sent_at: Mapped[datetime]
# ...
6.2 ایجاد API برای مشاهده تاریخچه
@router.get("/persons/{person_id}/sms-history")
def get_person_sms_history(...):
"""
دریافت تاریخچه پیامکهای ارسالی به یک Person
"""
pass
Phase 7: استفاده از SMS برای احراز هویت و تایید موبایل
این بخش استفاده از پیامک بهین اس ام اس برای موارد احراز هویت و امنیتی را پوشش میدهد.
7.1 تایید شماره موبایل (Mobile Verification)
مشابه Email Verification، باید سیستم تایید شماره موبایل هم پیادهسازی شود.
7.1.1 ایجاد مدل Mobile Verification
فایل جدید: hesabixAPI/adapters/db/models/mobile_verification.py
class MobileVerificationToken(Base):
__tablename__ = "mobile_verification_tokens"
id: Mapped[int]
user_id: Mapped[int]
mobile: Mapped[str] # شماره موبایل برای تایید
otp_code: Mapped[str] # کد 4 یا 6 رقمی
otp_hash: Mapped[str] # Hash شده برای امنیت
expires_at: Mapped[datetime]
verified_at: Mapped[datetime | None]
attempts: Mapped[int] # تعداد تلاشهای ناموفق
created_at: Mapped[datetime]
مشابه: EmailVerificationToken اما با OTP به جای token.
7.1.2 افزودن فیلد mobile_verified به User
Migration جدید:
# افزودن فیلد mobile_verified به جدول users
op.add_column('users', sa.Column('mobile_verified', sa.Boolean(),
nullable=False, server_default='0'))
7.1.3 Service برای Mobile Verification
فایل جدید: hesabixAPI/app/services/mobile_verification_service.py
class MobileVerificationService:
def __init__(self, db: Session):
self.db = db
notify_cfg = get_effective_notifications_settings(db)
self.sms_provider = SmsProvider(...)
def generate_otp(self) -> str:
"""تولید کد OTP 6 رقمی"""
import random
return str(random.randint(100000, 999999))
def create_mobile_verification(self, user_id: int, mobile: str) -> str:
"""
ایجاد کد OTP و ارسال پیامک
Returns: OTP code (فقط برای تست، در production نباید برگردانده شود)
"""
# 1. بررسی Rate Limiting (مثلاً حداکثر 3 بار در ساعت)
# 2. تولید OTP
# 3. Hash کردن OTP
# 4. ذخیره در دیتابیس
# 5. نرمالسازی شماره موبایل
# 6. ارسال پیامک
# 7. بازگرداندن OTP (برای تست)
pass
def verify_mobile_otp(self, user_id: int, otp_code: str) -> bool:
"""
تایید کد OTP
"""
# 1. دریافت آخرین token فعال
# 2. بررسی انقضا
# 3. بررسی تعداد تلاشها (حداکثر 5 تلاش)
# 4. Hash و مقایسه OTP
# 5. در صورت موفقیت: mark as verified و بهروزرسانی mobile_verified
pass
def resend_otp(self, user_id: int) -> str:
"""
ارسال مجدد OTP
"""
# مشابه create_mobile_verification اما با بررسی Rate Limiting
pass
7.1.4 API Endpoints
فایل: hesabixAPI/adapters/api/v1/auth.py
@router.post("/auth/send-mobile-verification")
def send_mobile_verification(
request: Request,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""
ارسال کد تایید به شماره موبایل کاربر
"""
# 1. دریافت شماره موبایل از user
# 2. بررسی وجود شماره موبایل
# 3. فراخوانی MobileVerificationService
# 4. ارسال پیامک
pass
@router.post("/auth/verify-mobile")
def verify_mobile(
payload: VerifyMobilePayload,
request: Request,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""
تایید شماره موبایل با کد OTP
"""
pass
@router.post("/auth/resend-mobile-verification")
def resend_mobile_verification(
request: Request,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""
ارسال مجدد کد تایید موبایل
"""
pass
7.1.5 بهروزرسانی ثبتنام
فایل: hesabixAPI/app/services/auth_service.py
def register_user(...):
# ...
# پس از ایجاد user
if mobile_n and is_mobile_verification_enabled(db):
# ایجاد mobile verification token
# ارسال OTP به شماره موبایل
# mobile_verified = False
else:
mobile_verified = True # اگر verification غیرفعال باشد
user = repo.create(
# ...
mobile_verified=mobile_verified
)
7.1.6 قالب پیامک OTP
کد تایید شما: {otp_code}
این کد تا {expires_minutes} دقیقه اعتبار دارد.
7.2 بازیابی کلمه عبور از طریق SMS
در حال حاضر بازیابی کلمه عبور فقط از طریق ایمیل انجام میشود. باید از طریق SMS هم امکانپذیر باشد.
7.2.1 بهروزرسانی Password Reset Service
فایل: hesabixAPI/app/services/auth_service.py
def create_password_reset(*, db: Session, identifier: str, ...):
# ...
# پس از ایجاد token
# اگر identifier موبایل است:
if mobile_n:
# ارسال پیامک با لینک reset یا OTP
send_password_reset_sms(db, user.id, mobile_n, token)
else:
# ارسال ایمیل (کد فعلی)
send_password_reset_email(...)
7.2.2 استفاده از OTP برای Reset Password
گزینه 1: استفاده از لینک در پیامک
برای بازیابی کلمه عبور روی لینک زیر کلیک کنید:
{reset_link}
یا از کد زیر استفاده کنید: {token}
گزینه 2: استفاده از OTP (بهتر)
- به جای token، یک OTP 6 رقمی ارسال شود
- کاربر OTP را وارد کند
- سپس کلمه عبور جدید را تنظیم کند
def create_password_reset_with_otp(*, db: Session, identifier: str, ...):
# ...
if mobile_n:
# تولید OTP
otp = generate_otp()
# ذخیره OTP hash
# ارسال OTP به موبایل
send_password_reset_otp_sms(mobile_n, otp)
# ...
7.2.3 API Endpoint جدید
@router.post("/auth/password-reset/verify-otp")
def verify_password_reset_otp(
payload: VerifyResetOtpPayload,
db: Session = Depends(get_db),
):
"""
تایید OTP بازیابی کلمه عبور
پس از تایید، یک token برگردانده میشود که کاربر میتواند با آن رمز جدید تنظیم کند
"""
pass
7.3 ورود با OTP (Login with OTP)
امکان ورود به سیستم بدون نیاز به رمز عبور، فقط با دریافت OTP از طریق SMS.
7.3.1 ایجاد Service
فایل جدید: hesabixAPI/app/services/otp_login_service.py
class OtpLoginService:
def __init__(self, db: Session):
self.db = db
# ...
def send_login_otp(self, mobile: str) -> tuple[bool, str | None]:
"""
ارسال OTP برای ورود
Returns: (success, session_id)
"""
# 1. نرمالسازی شماره موبایل
# 2. بررسی وجود کاربر با این شماره
# 3. Rate Limiting
# 4. تولید OTP
# 5. ایجاد session برای login
# 6. ارسال OTP
# 7. بازگرداندن session_id
pass
def verify_login_otp(self, session_id: str, otp_code: str) -> tuple[bool, User | None]:
"""
تایید OTP و ورود کاربر
Returns: (success, user)
"""
# 1. بررسی session
# 2. بررسی OTP
# 3. بررسی انقضا
# 4. ایجاد API Key (مانند login عادی)
# 5. بازگرداندن user و api_key
pass
7.3.2 مدل برای Login Session
فایل جدید: hesabixAPI/adapters/db/models/otp_login_session.py
class OtpLoginSession(Base):
__tablename__ = "otp_login_sessions"
id: Mapped[int]
session_id: Mapped[str] # شناسه منحصر به فرد session
mobile: Mapped[str]
user_id: Mapped[int | None] # بعد از شناسایی کاربر
otp_code_hash: Mapped[str]
attempts: Mapped[int]
expires_at: Mapped[datetime]
verified_at: Mapped[datetime | None]
ip_address: Mapped[str | None]
user_agent: Mapped[str | None]
created_at: Mapped[datetime]
7.3.3 API Endpoints
@router.post("/auth/login/send-otp")
def send_login_otp(
payload: SendLoginOtpPayload,
request: Request,
db: Session = Depends(get_db),
):
"""
ارسال OTP برای ورود
"""
pass
@router.post("/auth/login/verify-otp")
def verify_login_otp(
payload: VerifyLoginOtpPayload,
request: Request,
db: Session = Depends(get_db),
):
"""
تایید OTP و ورود
"""
pass
7.3.4 قالب پیامک Login OTP
کد ورود شما: {otp_code}
این کد تا 5 دقیقه اعتبار دارد.
7.4 احراز هویت دو مرحلهای (2FA)
افزودن 2FA برای امنیت بیشتر حساب کاربری.
7.4.1 افزودن فیلد به User
Migration:
# فعال/غیرفعال بودن 2FA
op.add_column('users', sa.Column('two_factor_enabled', sa.Boolean(),
nullable=False, server_default='0'))
# شماره موبایل برای 2FA (ممکن است با mobile اصلی متفاوت باشد)
op.add_column('users', sa.Column('two_factor_mobile', sa.String(32), nullable=True))
7.4.2 Service برای 2FA
فایل جدید: hesabixAPI/app/services/two_factor_service.py
class TwoFactorService:
def enable_2fa(self, user_id: int, mobile: str) -> bool:
"""
فعالسازی 2FA برای کاربر
"""
# 1. تایید شماره موبایل
# 2. فعال کردن 2FA
# 3. ذخیره شماره موبایل 2FA
pass
def send_2fa_otp(self, user_id: int) -> bool:
"""
ارسال OTP برای 2FA در هنگام ورود
"""
# 1. دریافت شماره موبایل 2FA
# 2. تولید OTP
# 3. ارسال پیامک
pass
def verify_2fa_otp(self, user_id: int, otp_code: str) -> bool:
"""
تایید OTP برای 2FA
"""
pass
7.4.3 بهروزرسانی Login Flow
فایل: hesabixAPI/app/services/auth_service.py
def login_user(...):
# ...
# پس از بررسی رمز عبور
if user.two_factor_enabled:
# ارسال OTP 2FA
# برگرداندن session برای 2FA verification
return {
"requires_2fa": True,
"session_id": "...",
"message": "لطفاً کد تایید ارسالی به موبایل را وارد کنید"
}
else:
# ورود عادی
# ...
7.4.4 API Endpoints
@router.post("/auth/2fa/enable")
def enable_2fa(
payload: Enable2FAPayload,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
فعالسازی 2FA
"""
pass
@router.post("/auth/2fa/verify")
def verify_2fa(
payload: Verify2FAPayload,
db: Session = Depends(get_db),
):
"""
تایید 2FA پس از ورود
"""
pass
7.5 تغییر شماره موبایل
امکان تغییر شماره موبایل کاربر با تایید OTP.
7.5.1 Service
def change_mobile(self, user_id: int, new_mobile: str) -> str:
"""
تغییر شماره موبایل کاربر
"""
# 1. نرمالسازی شماره جدید
# 2. بررسی تکراری نبودن
# 3. ارسال OTP به شماره جدید
# 4. ذخیره در session موقت
pass
def confirm_mobile_change(self, user_id: int, otp_code: str) -> bool:
"""
تایید تغییر شماره موبایل با OTP
"""
# 1. بررسی OTP
# 2. بهروزرسانی شماره موبایل
# 3. غیرفعال کردن mobile_verified (باید دوباره تایید شود)
pass
7.5.2 API Endpoint
@router.post("/auth/change-mobile")
def change_mobile(
payload: ChangeMobilePayload,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
درخواست تغییر شماره موبایل
"""
pass
@router.post("/auth/confirm-mobile-change")
def confirm_mobile_change(
payload: ConfirmMobileChangePayload,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
تایید تغییر شماره موبایل با OTP
"""
pass
7.6 Rate Limiting و امنیت
7.6.1 Rate Limiting برای OTP
- ارسال OTP: حداکثر 3 بار در ساعت برای هر شماره موبایل
- تایید OTP: حداکثر 5 تلاش ناموفق قبل از بلاک شدن
- بازیابی رمز: حداکثر 3 بار در 24 ساعت
7.6.2 ذخیرهسازی OTP
- OTP باید به صورت Hash ذخیره شود (نه Plain Text)
- استفاده از
hashlibیاbcryptبرای hash کردن
def hash_otp(otp: str) -> str:
settings = get_settings()
return hashlib.sha256(f"{settings.captcha_secret}:{otp}".encode()).hexdigest()
7.6.3 زمان انقضای OTP
- Mobile Verification OTP: 10 دقیقه
- Password Reset OTP: 15 دقیقه
- Login OTP: 5 دقیقه
- 2FA OTP: 5 دقیقه
7.7 قالبهای پیامک
قالبهای پیشنهادی:
- Mobile Verification:
کد تایید شماره موبایل شما: {otp_code}
اعتبار: {expires_minutes} دقیقه
- Password Reset:
کد بازیابی کلمه عبور: {otp_code}
این کد تا {expires_minutes} دقیقه اعتبار دارد.
- Login OTP:
کد ورود شما: {otp_code}
اعتبار: {expires_minutes} دقیقه
- 2FA:
کد تایید دو مرحلهای: {otp_code}
اعتبار: {expires_minutes} دقیقه
7.8 فایلهای مورد نیاز
Backend:
- ✅
adapters/db/models/mobile_verification.py- جدید - ✅
adapters/db/models/otp_login_session.py- جدید - ✅
adapters/db/repositories/mobile_verification_repo.py- جدید - ✅
adapters/db/repositories/otp_login_repo.py- جدید - ✅
app/services/mobile_verification_service.py- جدید - ✅
app/services/otp_login_service.py- جدید - ✅
app/services/two_factor_service.py- جدید - ✅
adapters/api/v1/auth.py- بهروزرسانی (افزودن Endpointهای جدید) - ✅
app/services/auth_service.py- بهروزرسانی (پشتیبانی از SMS) - ✅ Migration برای افزودن فیلدهای جدید
Frontend:
- ✅ صفحه تایید موبایل (Mobile Verification Page)
- ✅ Dialog برای وارد کردن OTP
- ✅ صفحه تنظیمات امنیتی (Security Settings)
- ✅ فعال/غیرفعال کردن 2FA
- ✅ صفحه ورود با OTP
- ✅ صفحه تغییر شماره موبایل
Phase 8: ویژگیهای پیشرفته (آینده)
8.1 دریافت وضعیت پیامک (Delivery Status)
با استفاده از GetMessageStatus میتوان وضعیت پیامکها را بررسی کرد.
پیشنهاد: یک Background Job برای بهروزرسانی وضعیت پیامکهای pending
7.2 دریافت پیامکهای ورودی (Inbox)
با استفاده از GetInboxMessage یا TrafficRelay میتوان پیامکهای دریافتی را دریافت کرد.
نیاز به:
- API Endpoint برای دریافت webhook از بهین اس ام اس
- ذخیره پیامکهای دریافتی در دیتابیس
- نمایش در UI
7.3 قالبهای پیامک (Templates)
ایجاد سیستم قالب برای پیامکهای رایج:
- "فاکتور شماره {number} به مبلغ {amount} ثبت شد"
- "واریز مبلغ {amount} انجام شد"
- "یادآوری بدهی: مبلغ {balance} تومان"
جریان کار (Workflow)
1. تنظیم اولیه توسط مدیر سیستم
- مدیر وارد صفحه تنظیمات سیستم میشود
- در بخش Notifications > Advanced
- SMS Provider را "behinsms" انتخاب میکند
- UserName و Password بهین اس ام اس را وارد میکند
- شماره اختصاصی (Sender) را وارد میکند
- تنظیمات را ذخیره میکند
2. ارسال پیامک از طریق سیستم ناتیفیکیشن
- سیستم میخواهد ناتیفیکیشن ارسال کند
NotificationService.send()فراخوانی میشود- اگر SMS در لیست کانالها باشد
SmsProvider.send_text()فراخوانی میشودBehinSmsProviderپیامک را از طریق API بهین اس ام اس ارسال میکند
3. ارسال پیامک به مشتری توسط کاربر
- کاربر وارد صفحه جزئیات مشتری میشود
- روی دکمه "ارسال پیامک" کلیک میکند
- Dialog باز میشود برای نوشتن پیام
- پیام را مینویسد و ارسال میکند
- Backend:
- شماره موبایل را دریافت میکند
- نرمالسازی میکند
- از طریق
PersonSmsServiceارسال میکند - در تاریخچه ثبت میکند
- نتیجه (موفقیت/خطا) به کاربر نمایش داده میشود
4. تایید شماره موبایل در ثبتنام
- کاربر با شماره موبایل ثبتنام میکند
- پس از ثبتنام، یک کد OTP به شماره موبایل ارسال میشود
- کاربر کد را دریافت میکند
- در صفحه تایید موبایل، کد را وارد میکند
- Backend:
- کد را بررسی میکند
- در صورت صحیح بودن،
mobile_verifiedرا بهTrueتنظیم میکند
- کاربر میتواند به حساب کاربری دسترسی کامل داشته باشد
5. بازیابی کلمه عبور از طریق SMS
- کاربر روی "فراموشی کلمه عبور" کلیک میکند
- شماره موبایل یا ایمیل خود را وارد میکند
- اگر شماره موبایل وارد شده:
- یک کد OTP 6 رقمی به موبایل ارسال میشود
- کاربر کد را وارد میکند
- پس از تایید، میتواند کلمه عبور جدید تنظیم کند
- اگر ایمیل وارد شده:
- لینک بازیابی به ایمیل ارسال میشود (مشابه قبل)
6. ورود با OTP
- کاربر در صفحه ورود، گزینه "ورود با کد یکبار مصرف" را انتخاب میکند
- شماره موبایل خود را وارد میکند
- یک کد OTP به موبایل ارسال میشود
- کاربر کد را وارد میکند
- Backend:
- کد را بررسی میکند
- در صورت صحیح بودن، یک API Key برای کاربر ایجاد میکند
- کاربر وارد سیستم میشود
7. احراز هویت دو مرحلهای (2FA)
- کاربر در تنظیمات امنیتی، 2FA را فعال میکند
- شماره موبایل برای دریافت کدهای 2FA را وارد میکند
- یک کد تایید به موبایل ارسال میشود
- پس از تایید، 2FA فعال میشود
- در ورودهای بعدی:
- کاربر رمز عبور را وارد میکند
- یک کد 2FA به موبایل ارسال میشود
- کاربر کد را وارد میکند
- سپس وارد سیستم میشود
نکات مهم امنیتی
- رمز عبور: باید در دیتابیس به صورت encrypted ذخیره شود
- API Key: در UI با
obscureTextنمایش داده شود - Rate Limiting: برای جلوگیری از سوء استفاده، محدودیت تعداد ارسال در روز
- IP Whitelist: در تنظیمات بهین اس ام اس، IP سرور باید اضافه شود
- لاگگذاری: تمام ارسالهای پیامک باید لاگ شوند (بدون ذخیره متن کامل در صورت حساس بودن)
تست و اعتبارسنجی
مراحل تست:
- تست تنظیمات: اتصال به API بهین اس ام اس با تنظیمات وارد شده
- تست GetCredit: دریافت اعتبار برای اطمینان از اتصال
- تست SendArray: ارسال یک پیامک تست
- تست GetMessageStatus: بررسی وضعیت پیامک ارسالی
- تست فرمت شماره: نرمالسازی شمارههای مختلف
- تست خطاها: بررسی رفتار سیستم در صورت خطا
مستندات مورد نیاز
- مستند API: توضیح Endpointهای جدید
- مستند کاربری: راهنمای استفاده برای مدیر سیستم
- مستند توسعهدهنده: نحوه اضافه کردن Provider جدید
نکات پیادهسازی
استفاده از HTTP Client
برای فراخوانی API بهین اس ام اس، میتوان از requests یا httpx استفاده کرد:
import httpx
async def send_array(...):
async with httpx.AsyncClient() as client:
params = {
"service": "SendArray",
"username": self.username,
"password": self.password,
"to": recipient_numbers_str, # comma-separated
"message": text,
"from": self.sender,
"IsFlashMessage": "true" if is_flash else "false",
}
if checking_message_id:
params["chkMessageId"] = checking_message_id
response = await client.get(self.BASE_URL, params=params)
# Parse response
مدیریت خطاها
def parse_response(response_text: str) -> tuple[bool, str | None, str | None]:
"""
Parse response from Behin SMS API
Returns: (is_success, message_id_or_code, error_message)
"""
try:
# اگر عدد است
result = int(response_text.strip())
if result < 50:
# کد خطا
error_msg = BEHINSMS_ERROR_CODES.get(result, "خطای نامشخص")
return False, str(result), error_msg
elif result >= 1000:
# MessageID موفق
return True, str(result), None
else:
# وضعیت نامشخص
return False, str(result), "وضعیت نامشخص"
except ValueError:
# ممکن است چند MessageID با کاما جدا شده باشد
parts = response_text.split(",")
if all(part.strip().isdigit() for part in parts):
# همه MessageID هستند
return True, response_text, None
else:
return False, None, "فرمت پاسخ نامعتبر"
خلاصه تغییرات فایلها
Backend (Python):
Provider و تنظیمات:
- ✅
app/services/providers/sms_provider.py- بهروزرسانی - ✅
app/services/providers/behin_sms_provider.py- جدید - ✅
app/services/system_settings_service.py- اضافه کردن فیلدها - ✅
adapters/api/v1/admin/system_settings.py- بهروزرسانی Payload
ارسال پیامک به مشتریان:
5. ✅ adapters/api/v1/business/persons.py - اضافه کردن Endpoint ارسال پیامک
6. ✅ app/services/person_sms_service.py - جدید
7. ✅ adapters/db/models/person_sms_history.py - جدید
احراز هویت و تایید موبایل:
8. ✅ adapters/db/models/mobile_verification.py - جدید
9. ✅ adapters/db/models/otp_login_session.py - جدید
10. ✅ adapters/db/repositories/mobile_verification_repo.py - جدید
11. ✅ adapters/db/repositories/otp_login_repo.py - جدید
12. ✅ app/services/mobile_verification_service.py - جدید
13. ✅ app/services/otp_login_service.py - جدید
14. ✅ app/services/two_factor_service.py - جدید
15. ✅ app/services/auth_service.py - بهروزرسانی (پشتیبانی از SMS)
API Endpoints:
16. ✅ adapters/api/v1/auth.py - افزودن Endpointهای احراز هویت
Migrations:
17. ✅ Migration برای افزودن mobile_verified به User
18. ✅ Migration برای افزودن two_factor_enabled و two_factor_mobile به User
19. ✅ Migration برای ایجاد جداول mobile_verification_tokens و otp_login_sessions
20. ✅ Migration برای ایجاد جدول person_sms_history
Frontend (Flutter):
تنظیمات:
- ✅
lib/pages/profile/notifications_settings_page.dart- اضافه کردن فیلدها - ✅
lib/pages/profile/security_settings_page.dart- جدید (تنظیمات امنیتی، 2FA)
ارسال پیامک به مشتریان:
3. ✅ lib/pages/business/person_detail_page.dart - اضافه کردن دکمه ارسال پیامک
4. ✅ lib/pages/business/persons_list_page.dart - اضافه کردن action menu
5. ✅ lib/services/person_sms_service.dart - جدید (Service برای فراخوانی API)
6. ✅ lib/widgets/sms/send_sms_dialog.dart - جدید (Dialog برای ارسال پیامک)
احراز هویت و تایید موبایل:
7. ✅ lib/pages/auth/mobile_verification_page.dart - جدید (تایید شماره موبایل)
8. ✅ lib/pages/auth/otp_login_page.dart - جدید (ورود با OTP)
9. ✅ lib/pages/auth/verify_otp_page.dart - جدید (صفحه عمومی برای تایید OTP)
10. ✅ lib/widgets/auth/otp_input_dialog.dart - جدید (Dialog برای وارد کردن OTP)
11. ✅ lib/services/mobile_verification_service.dart - جدید
12. ✅ lib/services/otp_login_service.dart - جدید
13. ✅ lib/services/two_factor_service.dart - جدید
اولویتبندی پیادهسازی
اولویت بالا (MVP):
- Phase 1: تکمیل تنظیمات سیستم
- Phase 2: پیادهسازی Behin SMS Provider
- Phase 3: فرمتسازی شماره تلفن
- Phase 4: مدیریت خطاها
- Phase 7.1: تایید شماره موبایل (Mobile Verification)
- Phase 7.2: بازیابی کلمه عبور از طریق SMS
اولویت متوسط:
- Phase 5.1 و 5.3: API و Service برای ارسال به Person
- Phase 5.2: UI برای ارسال پیامک
- Phase 6: ثبت تاریخچه
- Phase 7.3: ورود با OTP (Login with OTP)
اولویت پایین (Future):
- Phase 7.4: احراز هویت دو مرحلهای (2FA)
- Phase 7.5: تغییر شماره موبایل
- Phase 8: ویژگیهای پیشرفته (Delivery Status, Inbox, Templates)
نویسنده: AI Assistant
تاریخ: 2024
وضعیت: Draft - آماده برای بررسی و پیادهسازی