93 lines
8.9 KiB
Markdown
93 lines
8.9 KiB
Markdown
# طرح افزودن درگاه پیامکی جدید (Relay / `createsms`)
|
||||
|
|
|
|||
|
|
> وضعیت: **اجرا شد** (2026-10-05) — تصمیم ثبتشده: D26.
|
|||
|
|
> تاریخ: 2026-10-05
|
|||
|
|
|
|||
|
|
## ۱. کد ورودی چه میکند
|
|||
|
|
|
|||
|
|
کد PHP ارسالی (Laravel) یک پیامک را به یک سرویس واسط (relay) میفرستد:
|
|||
|
|
|
|||
|
|
| مورد | مقدار |
|
|||
|
|
|---|---|
|
|||
|
|
| متد / آدرس | `POST http://185.231.112.101/api/createsms` (HTTP ساده، بدون TLS) |
|
|||
|
|
| بدنه | JSON |
|
|||
|
|
| هدر احراز هویت | `secret_key: <کلید>` |
|
|||
|
|
| timeout | ۱۵ ثانیه |
|
|||
|
|
| فیلد `phone` | شماره گیرنده |
|
|||
|
|
| فیلد `text` | متن پیام، `trim()` شده |
|
|||
|
|
| فیلد `host` | `Controller::Config('montek')` و اگر خالی بود IP خود سرور (`gethostbyname(gethostname())`) — یعنی شناسهی «این مشتری/سرور» نزد سرویس واسط |
|
|||
|
|
| فیلد `platform` | اگر `$sendFromKave` درست باشد `"kave"` (کاوهنگار)، وگرنه `null` (خط پیشفرض سرویس واسط) |
|
|||
|
|
| `CURLOPT_IPRESOLVE = V4` | فقط اجبار IPv4؛ چون آدرس خودش IP است در عمل اثری ندارد |
|
|||
|
|
|
|||
|
|
این کد در `henza_server` وجود ندارد (جستجو شد)، پس از سیستم دیگری آمده و مرجع parity ندارد.
|
|||
|
|
|
|||
|
|
## ۲. کجا قرار میگیرد
|
|||
|
|
|
|||
|
|
ماژول پیامک همین الان در [apps/api/src/modules/sms/sms.service.ts](../apps/api/src/modules/sms/sms.service.ts) هست و همهی مصرفکنندهها (OTP ورود، پیامک خوشآمد، اعلان تکمیل پروفایل، …) فقط `SmsService.send()` را صدا میزنند. پس:
|
|||
|
|
|
|||
|
|
- **هیچ مصرفکنندهای تغییر نمیکند.** فقط یک «درایور» دوم کنار Trez اضافه میشود و با `SMS_PROVIDER=relay` انتخاب میشود.
|
|||
|
|
- قوانین فعلی حفظ میشود: `send()` هرگز throw نمیکند (D20)، هر پیام در `SmsMessage` ثبت میشود، متن OTP ذخیره نمیشود (SEC-11)، نبود تنظیمات فقط هشدار میدهد و سرور بالا میآید.
|
|||
|
|
|
|||
|
|
## ۳. تغییرات پیشنهادی
|
|||
|
|
|
|||
|
|
### ۳.۱ کد — `sms.service.ts`
|
|||
|
|
1. `smsConfig()` متغیرهای جدید را هم میخواند (جدول ۳.۲).
|
|||
|
|
2. `smsConfigured()` برای `relay`: کافی است `SMS_RELAY_SECRET` و `SMS_RELAY_HOST` پر باشند.
|
|||
|
|
3. `assertSmsConfigured()` مقدار `relay` را میشناسد و متغیرهای خالی را با نام گزارش میکند (بدون throw).
|
|||
|
|
4. متد جدید `deliverViaRelay(mobile, text, platform)`:
|
|||
|
|
- `fetch` با `Content-Type: application/json` و هدر `secret_key`، بدنهی `{ phone, host, text, platform }`، timeout ۱۵ ثانیه با `AbortController` — همشکل `deliverViaTrez`.
|
|||
|
|
- موفقیت = پاسخ HTTP 2xx (تا وقتی قالب پاسخ سرویس واسط مشخص شود — سؤال ۴).
|
|||
|
|
5. `SmsSendOptions` یک گزینهی اختیاری `platform?: 'kave'` میگیرد؛ درایور Trez آن را نادیده میگیرد.
|
|||
|
|
6. اجبار IPv4 پیاده نمیشود چون آدرس IP است؛ اگر بعداً دامنه شد، با `dns.lookup` با `family: 4` اضافه میشود.
|
|||
|
|
|
|||
|
|
### ۳.۲ متغیرهای محیطی جدید
|
|||
|
|
|
|||
|
|
| متغیر | اجباری | توضیح |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `SMS_PROVIDER` | — | مقدار جدید `relay` |
|
|||
|
|
| `SMS_RELAY_URL` | خیر | پیشفرض `http://185.231.112.101/api/createsms` |
|
|||
|
|
| `SMS_RELAY_SECRET` | **بله** | مقدار هدر `secret_key` — **فقط در env، هرگز در کد یا git** |
|
|||
|
|
| `SMS_RELAY_HOST` | **بله** | مقدار فیلد `host` (معادل `Config('montek')`) |
|
|||
|
|
| `SMS_RELAY_KAVE_FOR` | خیر | کدام پیامها از کاوهنگار بروند — بسته به پاسخ سؤال ۲ |
|
|||
|
|
|
|||
|
|
### ۳.۳ مستندات و استقرار
|
|||
|
|
- `.env.example` (ریشه)، `apps/api/.env.example`، `docker-compose.yml`، `DELIVERY.md` (بخش SMS و گام ۴): افزودن متغیرهای بالا.
|
|||
|
|
- `docs/parity/DECISIONS.md`: ثبت یک تصمیم جدید (D26) برای درایور relay.
|
|||
|
|
|
|||
|
|
### ۳.۴ تست — `sms.service.spec.ts`
|
|||
|
|
- تنظیمات: پیشفرض URL، `smsConfigured` با/بدون secret و host، هشدار با نام متغیرهای خالی.
|
|||
|
|
- ارسال با `fetch` ماکشده: آدرس، هدر `secret_key`، بدنهی JSON، `trim` متن، `platform` درست؛ و اینکه خطای شبکه/timeout/HTTP 500 به `{ ok: false }` تبدیل شود و throw نشود.
|
|||
|
|
|
|||
|
|
## ۴. هشدار امنیتی ⚠
|
|||
|
|
|
|||
|
|
1. **کلید `secret_key` لو رفته فرض شود.** در متن پیام چت آمده و احتمالاً در git پروژهی PHP هم هست (مشابه SEC-12). باید از صاحب سرویس واسط کلید جدید گرفت و فقط در env سرور گذاشت. این کلید در کد این مخزن نوشته نمیشود.
|
|||
|
|
2. **ارتباط HTTP ساده است**؛ کلید و متن پیام (از جمله کد OTP) رمزنشده از اینترنت عبور میکنند. اگر سرویس واسط HTTPS دارد از آن استفاده شود.
|
|||
|
|
|
|||
|
|
## ۵. سؤالها (با پیشنهاد)
|
|||
|
|
|
|||
|
|
1. **جایگزین یا کنار Trez؟** پیشنهاد: کنار Trez بماند و با `SMS_PROVIDER=relay` انتخاب شود؛ پیشفرض همان `trez` بماند تا رفتار فعلی عوض نشود. یا پیشفرض `relay` شود؟
|
|||
|
|
2. **`sendFromKave` کی درست است؟** در کد اصلی این متغیر از کجا مقدار میگیرد؟ پیشنهاد: فقط OTP ورود از کاوهنگار برود (معمولاً برای سرعت/تحویل بهتر) و بقیه از خط پیشفرض — قابل تنظیم با env.
|
|||
|
|
3. **مقدار `host` (`montek`) چیست؟** پیشنهاد: اجباری در env. fallback به IP خود سرور پیاده نمیشود، چون داخل Docker، IP کانتینر (مثلاً `172.x`) برمیگردد که برای سرویس واسط بیمعناست.
|
|||
|
|
4. **پاسخ سرویس واسط چه شکلی است؟** اگر در خطا هم 200 برمیگرداند (مثلاً `{"status": false}`)، نمونهی پاسخ موفق و ناموفق لازم است تا `ok` درست تشخیص داده شود. پیشنهاد فعلی: فقط وضعیت HTTP.
|
|||
|
|
5. **HTTPS دارد؟** (بند ۴.۲)
|
|||
|
|
|
|||
|
|
## ۷. پاسخهای مالک (2026-10-05)
|
|||
|
|
|
|||
|
|
| سؤال | پاسخ | اثر روی پیادهسازی |
|
|||
|
|
|---|---|---|
|
|||
|
|
| کلید | لو نرفته؛ تأیید دومرحلهای جای دیگری هست؛ داخل پروژه قرار بگیرد | کلید بهعنوان مقدار پیشفرض در کد میآید و `SMS_RELAY_SECRET` فقط آن را بازنویسی میکند. بند ۴.۱ لغو شد. |
|
|||
|
|
| ۱. جایگزین Trez؟ | پیشفرض `relay` شود؛ Trez حذف نشود؛ ممکن است بعداً سرویسهای بیشتری بیاید | سرویس به ساختار «درایور» بازنویسی میشود: یک اینترفیس `SmsDriver` و هر سرویس یک فایل جدا (`drivers/relay.driver.ts`، `drivers/trez.driver.ts`). برای اضافه کردن سرویس بعدی فقط یک فایل و یک خط ثبت لازم است. پیشفرض `SMS_PROVIDER` برابر `relay` میشود. |
|
|||
|
|
| ۳. `host` | IP سرور درست است | `SMS_RELAY_HOST` اختیاری است. اگر خالی باشد، مثل PHP اولین IPv4 غیرداخلی سرور فرستاده میشود. |
|
|||
|
|
| ۴. پاسخ | با تشخیص خودم | `ok` فقط وقتی است که HTTP 2xx باشد و بدنهی JSON هم نشانهی شکست صریح نداشته باشد (`status: false`، `success: false`، `error`). در غیر این صورت `ok: false` + لاگ؛ هیچوقت throw نمیشود. |
|
|||
|
|
| ۵. HTTPS | دارد | بررسی شد: روی `https://185.231.112.101` گواهی `*.montpar.ir` نصب است که **از 2026-08-22 منقضی شده** و روی IP هم معتبر نیست ← سؤال ب |
|
|||
|
|
|
|||
|
|
### سؤالهای باز
|
|||
|
|
- **الف) `sendFromKave`:** کدام پیامها از کاوهنگار بروند؟ پیشنهاد: فقط OTP ورود.
|
|||
|
|
- **ب) آدرس HTTPS:** نام دامنهی سرویس (زیرمجموعهای از `montpar.ir`) چیست؟ گواهی هم باید تمدید شود. تا آن موقع پیشفرض همان `http://185.231.112.101/api/createsms` میماند و با `SMS_RELAY_URL` عوض میشود.
|
|||
|
|
|
|||
|
|
## ۶. وضعیت اجرا
|
|||
|
|
|
|||
|
|
- [x] تأیید طرح و پاسخ سؤالها (الف: همهی پیامها از کاوهنگار؛ ب: همان IP و HTTP درست است)
|
|||
|
|
- [x] کد + تست (`apps/api/src/modules/sms/drivers/`، ۱۳۵ تست سبز)
|
|||
|
|
- [x] env / compose / DELIVERY / DECISIONS (D26) / ISSUES (REL-02)
|