ADR-021: AI Photo Studio — интеграция через cross-origin iframe + postMessage
Контекст
ai-photo-studio — самостоятельный продукт (ai-photo-studio-frontend + ai-photo-studio-backend aka gensvc). На вход — фото блюда + пресет, на выход — webp 1024×1024, сгенерированный AI (Polza.ai / Gemini Flash Image). Нужна интеграция с erp-admin: маркетолог открывает карточку товара → жмёт «Улучшить с помощью AI» → модалка с фотостудией → результат применяется к product.image_url.
Развилки и решения:
1. Хостинг — same-origin или отдельный домен
- ✗ same-origin под
/photo-studio/за admin-nginx — пришлось бы править admin nginx + Vite BASE_PATH + копировать сертификат - ✓ отдельные поддомены
ai-photostudio-test.nirbi.ru(frontend) иai-photostudio-api-test.nirbi.ru(backend)
Прод: ai-photostudio.nirbi.ru / ai-photostudio-api.nirbi.ru.
Минусы — cross-origin, требуется CORS на API + targetOrigin в postMessage. Принимаемо.
2. Auth crypto — JWKS RS256 или shared HS256
ERP auth-service подписывает JWT через HS256 (симметричный секрет в app.jwt.secret). AI-backend изначально умел JWKS RS256.
- ✗ переезд auth-service на RS256 — затрагивает 12 сервисов, ~3-5 дней работы
- ✗ JWKS endpoint только для gensvc-токенов — отдельная RSA-пара, +код в auth-service
- ✓ gensvc мигрирует под HS256 (коллега со стороны фотостудии). gensvc запускается с
AUTH_MODE=local+JWT_LOCAL_SECRET = app.jwt.secret из ERP auth-service
Секрет лежит на VPS в env обоих сервисов, в git не попадает. Серверный, не клиентский — leak risk минимальный.
3. Permissions — endpoint user-service или claim в JWT
По Roles.md в ERP permissions не кладутся в JWT — внутренние сервисы получают через user-service endpoint. Но gensvc — внешний downstream и ходить в user-service не должен.
- ✓ в JWT добавлен claim
permissions: [...]— только для внешних потребителей. Внутренние сервисы продолжают использовать endpoint.
JwtService.generateAccessToken принял дополнительный параметр permissions. Все 3 callsites (login, pinLogin, refresh) обновлены.
4. Передача результата из iframe в parent
- ✓ postMessage с явным targetOrigin (parent_origin приходит query-параметром в iframe
?parent_origin=https://erp-test.nirbi.ru). Не*— иначе любой сторонний фрейм может прочитать presignedoutput_url. - Parent фильтрует входящие сообщения по
e.origin === VITE_PHOTO_STUDIO_URL.
События:
photo-studio:apply—{job_id, output_url, preset_id}— родитель скачивает webp по presigned URL и заливает через существующийPOST /api/v1/admin/catalog/products/{id}/image(multipart). Reuse уже работающего пути.photo-studio:cancel— родитель закрывает модалку.
5. Embed-mode фотостудии
В iframe не нужны header/nav/«История». Переключение через ?embed=1 в URL → сохраняется в sessionStorage → Shell не рендерит header.
Последствия
Плюсы:
- gensvc остался самостоятельным продуктом (можно отдать клиенту в чистом виде)
- erp-admin не зависит от внутренней реализации gensvc
- postMessage-контракт документирован, расширяем (можно добавить
apply-multiple,progressetc.) - Auth — никаких новых endpoint’ов в ERP, токен передаётся в iframe через query
Минусы:
- Нужны отдельные TLS-сертификаты (certbot) и DNS A-records на каждый поддомен — операционная нагрузка на каждом окружении.
- Cross-origin sessionStorage — admin не может revoked-listить токены фотостудии. Принимаемо: TTL короткий, gensvc валидирует самостоятельно.
- 24-часовой presigned
output_url— если юзер уйдёт на час до клика «Применить», ссылка может истечь. Mitigation: при открытии модалки токен выдан со свежим TTL, на success сразу применяем.
Связанные
- AI Photo Studio integration (decomposition) — TBD
- Repositories — статусы
ai-photo-studio-* - Роли — обоснование permissions claim в JWT