Реализована интеграция с СМС провайдером

This commit is contained in:
mi
2026-07-23 11:49:15 +03:00
parent cc0163eb94
commit b1ed714d5b
89 changed files with 5934 additions and 202 deletions
@@ -372,7 +372,7 @@ MESSAGE_SAFETY_REDIS_URL=redis://message_safety:<REDIS_SAFETY_PASSWORD>@redis:63
### 8.5. Mock OTP
В MVP реализован только mock OTP. Для запуска:
В текущих deploy-артефактах реализован только mock OTP. Для запуска до controlled SMS rollout:
```dotenv
KEYCLOAK_OTP_MOCK_ENABLED=true
@@ -383,6 +383,10 @@ KEYCLOAK_OTP_MOCK_CODE=<ТЕСТОВЫЙ_КОД_НЕ_КОРОЧЕ_16_СИМВО
Этот код будет вводиться пользователем при тестовой авторизации. Не используйте
его как production-механизм доставки OTP.
Целевой real mode задаёт `modules/module-11-idgtl-sms.md`: Keycloak генерирует и локально проверяет OTP, `sms-service` надёжно записывает заказ/журнал, worker вызывает i-Digital Direct, callback обновляет только delivery journal. Нельзя просто установить `KEYCLOAK_OTP_MOCK_ENABLED=false`.
До переключения необходимы: schema/role `sms` и migrations/seed, active approved `auth_otp` (`code`, `ttl_min`), согласованный sender, Direct `TOKEN_1`, парные service tokens, отдельные callback credentials, exact nginx callback route, подтверждённый source IP Direct и статический egress IP worker. Сначала deploy при mock=true, затем provider smoke/callback/redaction evidence и только после этого cutover. Rollback возвращает mock без удаления SMS schema/journal.
### 8.6. S3
```dotenv
+6
View File
@@ -211,6 +211,12 @@ SCHEMA_BACKWARD_COMPATIBLE_CONFIRMED=true \
ENV_FILE=/secure/path/previous-release.env deployment/scripts/smoke.sh
```
## Real SMS rollout addendum
This runbook remains mock-only until module-11 artifacts exist. An SMS release requires schema/role `sms`, versioned migrations and an active approved `auth_otp` seed, `sms-service`/worker, the exact callback route, paired service tokens, Direct `TOKEN_1`, approved sender/template, separate callback credentials, a reconfirmed callback source IP, and a static worker egress IP.
Order: App DB OTP seed → SMS schema/migrations/seed → mock Direct tests → production SMS deployment while Keycloak remains in mock mode → Keycloak expand migration/SPI → controlled provider smoke plus callback/redaction evidence → real mode. Roll back by restoring mock mode without deleting the journal/schema; stop new real orders and drain or record in-flight/`uncertain` rows. Downgrade only with proven schema compatibility.
Never run Alembic downgrade. After a backward-incompatible migration choose a
forward fix or coordinated PITR/S3/Bitrix reconciliation under maintenance.
Always verify outbox/inbox/recovery so an ambiguous message is not sent twice.
@@ -216,6 +216,12 @@ SCHEMA_BACKWARD_COMPATIBLE_CONFIRMED=true \
ENV_FILE=/secure/path/previous-release.env deployment/scripts/smoke.sh
```
## Дополнение: rollout реальной SMS-авторизации
Текущий runbook остаётся mock-only, пока артефакты module-11 не реализованы. Для SMS release обязательны: schema/role `sms`, migrations/seed active approved `auth_otp`, `sms-service`/worker, exact callback route, парные service tokens, Direct `TOKEN_1`, согласованные sender/template, отдельные callback credentials, подтверждённый callback source IP и статический egress IP worker.
Порядок: App DB OTP seed → SMS schema/migrations/seed → test с mock Direct → production SMS deploy при `KEYCLOAK_OTP_MOCK_ENABLED=true` → Keycloak expand migration/SPI → provider smoke и callback/redaction evidence → real mode. Rollback: вернуть mock, не удалять journal/schema, остановить новые real orders и зафиксировать in-flight/`uncertain`; downgrade только при доказанной совместимости.
Никогда не выполняйте downgrade Alembic. После обратно несовместимой миграции используйте
исправление вперед либо согласованный PITR с восстановлением S3 и сверкой Bitrix во время
технического обслуживания. Всегда проверяйте outbox, inbox и recovery, чтобы сообщение
@@ -5,6 +5,9 @@ settings:
otp.phone.max_send_attempts_per_24h: {type: integer, value: 3, public: false}
otp.phone.min_seconds_between_attempts: {type: integer, value: 30, public: false}
otp.phone.max_verify_attempts: {type: integer, value: 5, public: false}
otp.phone.code_length: {type: integer, value: 6, public: false}
otp.phone.ttl_seconds: {type: integer, value: 60, public: false}
otp.phone.sms_order_timeout_ms: {type: integer, value: 3000, public: false}
operator.call.phone: {type: string, value: "+74999591007", public: true}
consent.personal_data.required: {type: boolean, value: true, public: true}
consent.personal_data.document_url: {type: string, value: "https://www.han0107.ru/privacy/persdata-agree-mobile", public: true}
@@ -1,3 +1,11 @@
x-no-sms-secrets: &no-sms-secrets
SMS_DATABASE_URL: ""
SMS_SERVICE_TOKEN: ""
KEYCLOAK_SMS_SERVICE_TOKEN: ""
IDGTL_SMS_API_KEY: ""
IDGTL_SMS_CALLBACK_USERNAME: ""
IDGTL_SMS_CALLBACK_PASSWORD: ""
services:
migrate-api:
image: ${API_BACKEND_IMAGE:-han-chat-api-backend:local}
@@ -5,6 +13,7 @@ services:
env_file:
- path: ../.env
required: false
environment: *no-sms-secrets
entrypoint: []
command: ["alembic", "upgrade", "head"]
volumes:
@@ -19,6 +28,7 @@ services:
env_file:
- path: ../.env
required: false
environment: *no-sms-secrets
entrypoint: []
command: ["alembic", "upgrade", "head"]
volumes:
@@ -33,6 +43,25 @@ services:
env_file:
- path: ../.env
required: false
environment: *no-sms-secrets
entrypoint: []
command: ["alembic", "upgrade", "head"]
volumes:
- ${PG_CA_HOST_PATH}:/run/secrets/pg-ca.pem:ro
networks: [backend, egress]
restart: "no"
security_opt: ["no-new-privileges:true"]
migrate-sms:
image: ${SMS_SERVICE_IMAGE:-han-chat-sms-service:local}
profiles: ["ops"]
environment:
SMS_DATABASE_URL: ${SMS_DATABASE_URL}
SMS_SERVICE_TOKEN: ${SMS_SERVICE_TOKEN}
IDGTL_SMS_BASE_URL: ${IDGTL_SMS_BASE_URL:-https://direct.i-dgtl.ru}
IDGTL_SMS_CALLBACK_PUBLIC_URL: ${IDGTL_SMS_CALLBACK_PUBLIC_URL}
IDGTL_SMS_CALLBACK_USERNAME: ${IDGTL_SMS_CALLBACK_USERNAME}
IDGTL_SMS_CALLBACK_PASSWORD: ${IDGTL_SMS_CALLBACK_PASSWORD}
entrypoint: []
command: ["alembic", "upgrade", "head"]
volumes:
@@ -47,6 +76,7 @@ services:
env_file:
- path: ../.env
required: false
environment: *no-sms-secrets
entrypoint: []
command:
- /bin/sh
@@ -12,7 +12,9 @@ docker compose --env-file "${ENV_FILE:-.env}" config --quiet
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-api alembic current
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-bitrix-local alembic current
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-bitrix-sync alembic current
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-sms alembic current
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-api
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-bitrix-local
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-bitrix-sync
docker compose --env-file "${ENV_FILE:-.env}" --profile ops run --rm migrate-sms
echo "Migrations completed; record revisions in release evidence."
@@ -42,6 +42,12 @@ curl -fsS "${PUBLIC_WEB_URL}/auth/realms/${KEYCLOAK_REALM}/.well-known/openid-co
internal_code=$(curl -sS -o /dev/null -w '%{http_code}' "${PUBLIC_WEB_URL}/internal/safety/v1/messages/check")
[ "$internal_code" = "404" ] || { echo "Public /internal returned $internal_code, expected 404" >&2; exit 1; }
sms_internal_code=$(curl -sS -o /dev/null -w '%{http_code}' "${PUBLIC_WEB_URL}/internal/sms/v1/messages/00000000-0000-0000-0000-000000000000")
[ "$sms_internal_code" = "404" ] || { echo "Public SMS internal API returned $sms_internal_code, expected 404" >&2; exit 1; }
sms_callback_code=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
-H 'Content-Type: application/json' --data '[]' \
"${PUBLIC_WEB_URL}/callbacks/idgtl/sms")
[ "$sms_callback_code" = "403" ] || { echo "SMS callback without provider IP returned $sms_callback_code, expected 403" >&2; exit 1; }
headers=$(curl -fsSI "${PUBLIC_WEB_URL}/")
printf '%s' "$headers" | grep -qi '^x-content-type-options: nosniff'