5.5 KiB
Отправка СМС
На ВМ выполните:
cd /opt/han-chat/backend umask 077
read -r -p "Тестовый номер в E.164 (+79...): " TEST_PHONE
CHALLENGE_ID=$(python3 -c 'import uuid; print(uuid.uuid4())') OTP_CODE=$(python3 -c 'import secrets; print(f"{secrets.randbelow(1000000):06d}")')
SMS_TOKEN=$(python3 - <<'PY' from pathlib import Path
for line in Path(".env").read_text().splitlines(): if line.startswith("SMS_SERVICE_TOKEN="): print(line.split("=", 1)[1].strip().strip(""'")) break else: raise SystemExit("SMS_SERVICE_TOKEN отсутствует") PY )
export TEST_PHONE CHALLENGE_ID OTP_CODE SMS_TOKEN REQUEST_FILE=$(mktemp)
python3 - "$REQUEST_FILE" <<'PY' import json import os import sys
payload = { "idempotency_key": f"ops:smoke:{os.environ['CHALLENGE_ID']}", "template_code": "auth_otp", "locale": "ru", "phone_e164": os.environ["TEST_PHONE"], "substitutions": { "code": os.environ["OTP_CODE"], "ttl_min": "1", }, "customer_ref": os.environ["CHALLENGE_ID"], "message_ttl_sec": 60, }
with open(sys.argv[1], "w", encoding="utf-8") as file: json.dump(payload, file, ensure_ascii=False) PY
Создайте функцию отправки:
send_sms_smoke() {
docker compose --env-file .env --profile ops run --rm --no-deps
--user 0:0
--entrypoint sh
-e SMS_TOKEN
-v "$REQUEST_FILE:/tmp/sms-request.json:ro"
toolbox -ec '
curl -sS
-w "\nHTTP %{http_code}\n"
-X POST
-H "Authorization: Bearer $SMS_TOKEN"
-H "Content-Type: application/json"
-H "X-Request-ID: ops-sms-smoke"
--data-binary @/tmp/sms-request.json
http://sms-service:8080/internal/sms/v1/send
'
}
Отправка:
send_sms_smoke
Ожидается: HTTP 202 и JSON с sms_message_id.
Проверьте журнал:
SELECT id, phone_masked, send_status, delivery_status, provider_message_id, provider_error_code, attempt_count, created_at FROM sms.sms_outbound_message ORDER BY created_at DESC LIMIT 5;
После проверки удалите секретные данные:
shred -u "$REQUEST_FILE" 2>/dev/null || rm -f "$REQUEST_FILE" unset SMS_TOKEN OTP_CODE TEST_PHONE CHALLENGE_ID REQUEST_FILE
Тесты
Выполняйте на ВМ из /opt/han-chat/backend.
1. Проверить запрет публичного internal API
PUBLIC_WEB_URL=$(python3 - <<'PY'
from pathlib import Path
for line in Path(".env").read_text().splitlines():
if line.startswith("PUBLIC_WEB_URL="):
print(line.split("=", 1)[1].strip().strip("\"'"))
break
PY
)
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
"$PUBLIC_WEB_URL/internal/sms/v1/messages/00000000-0000-0000-0000-000000000000"
Ожидается:
HTTP 404
2. Проверить callback с неправильного IP
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
-X POST \
-H 'Content-Type: application/json' \
--data '[]' \
"$PUBLIC_WEB_URL/callbacks/idgtl/sms"
Ожидается:
HTTP 403
Заголовок X-Forwarded-For не должен позволять обойти ограничение.
3. Проверить Basic auth внутри Docker-сети
Получите credentials из .env:
CB_USER=$(python3 - <<'PY'
from pathlib import Path
for line in Path(".env").read_text().splitlines():
if line.startswith("IDGTL_SMS_CALLBACK_USERNAME="):
print(line.split("=", 1)[1].strip().strip("\"'"))
break
PY
)
CB_PASS=$(python3 - <<'PY'
from pathlib import Path
for line in Path(".env").read_text().splitlines():
if line.startswith("IDGTL_SMS_CALLBACK_PASSWORD="):
print(line.split("=", 1)[1].strip().strip("\"'"))
break
PY
)
export CB_USER CB_PASS
Неверные credentials:
docker compose --env-file .env --profile ops run --rm --no-deps \
--entrypoint sh toolbox -ec '
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
-u invalid:invalid \
-H "Content-Type: application/json" \
--data "[]" \
http://sms-service:8080/callbacks/idgtl/sms
'
Ожидается HTTP 401.
Правильные credentials:
docker compose --env-file .env --profile ops run --rm --no-deps \
--entrypoint sh -e CB_USER -e CB_PASS toolbox -ec '
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
-u "$CB_USER:$CB_PASS" \
-H "Content-Type: application/json" \
--data "[]" \
http://sms-service:8080/callbacks/idgtl/sms
'
Ожидается HTTP 422: авторизация прошла, но пустой callback-массив невалиден.
После проверки:
unset CB_USER CB_PASS
4. Проверить реальный callback Direct
После тестовой SMS:
SELECT
id,
send_status,
delivery_status,
provider_message_id,
callback_last_at,
sent_at,
delivered_at
FROM sms.sms_outbound_message
ORDER BY created_at DESC
LIMIT 5;
Успешный реальный callback подтверждается:
callback_last_at IS NOT NULL;delivery_status = sentилиdelivered;- заполняются
sent_at/delivered_at.
Дополнительно:
docker compose --env-file .env logs --since=30m nginx sms-service
Для callback должен быть ответ 204. Только реальный запрос Direct может полноценно подтвердить IP allowlist.