BR 2.6 — Photo Studio Service

Репозиторий

nearbyErp/ai-photo-studio-backend (Go), ветка feature/transform-mode

Контекст

Рабочий прототип на Go. Auth — JWKS / local HS256 + UserInfo enrichment (см. ADR-021). Polza интеграция переведена на async polling (см. ADR-022).

Прогресс на 2026-05-08

Реализованы и закоммичены: enricher, optional iss/aud, Polza async:true, polling 15 мин, smart fallback, IDOR fix на SSE, atomic quota для user-presets, no-block UX на студии, live-history с уведомлениями. Остаётся: introspection-валидатор после получения service-token, Gateway-rewrite на проде, reconciliation worker для долгих Polza-задач.

Задачи

1. Добавить IntrospectionValidator

  • Создать файл internal/auth/introspection.go
  • Реализовать IntrospectionValidator — вызывает POST {AUTH_SERVICE_URL}/internal/auth/validate с заголовком X-Service-Token: {AUTH_SERVICE_TOKEN}
  • Обновить структуру Claims (internal/auth/claims.go): добавить поле Scope (опциональный, scope.type + scope.legal_entity_ids / scope.store_ids из ответа validate)
  • Реализовать retry + timeout: 3 попытки, timeout 500 мс на запрос
  • При ошибке вызова Auth Service возвращать 503 Service Unavailable (не 401 — это разные ситуации)

2. Redis-кэш для introspection-ответов

  • При успешном ответе /internal/auth/validate кэшировать результат в Redis по ключу introspect:{sha256_of_token} с TTL 60 сек
  • При cache-hit — не вызывать Auth Service, брать данные из Redis
  • Использовать существующий Redis-клиент из deps (Redis уже используется для очереди заданий)

3. Переменная AUTH_MODE в конфигурации

  • Добавить env-переменную AUTH_MODE (допустимые значения: introspection, jwks)
  • При AUTH_MODE=introspection — инициализировать IntrospectionValidator вместо JWKSValidator
  • Дополнительные переменные для introspection:
    • AUTH_SERVICE_URL — URL Auth Service (например http://auth-service:3001)
    • AUTH_SERVICE_TOKEN — service token для X-Service-Token header
  • Обновить .env.example с новыми переменными
  • В docker-compose.ymldocker-compose.prod.yml) задать AUTH_MODE=introspection для production-окружения

4. Выровнять URL-префикс для API Gateway

Это изменение затрагивает фронтенд Photo Studio

При смене URL-пути с /v1/ на /api/v1/gensvc/ существующий фронт (если есть) сломается. Координировать с командой фронтенда.

  • Вариант A принят (2026-05-07): API Gateway rewrite {gateway}/api/v1/gensvc/*{service}:8080/v1/*. Код сервиса не меняется, фронт продолжает работать на /v1/... относительные пути.
  • Зафиксировать в Gateway-конфиге маршрут /api/v1/gensvc/* с rewrite rule
  • Прописать в Overview.md (этого сервиса) URL-маппинг для сторонних потребителей (Admin BFF, фронт-клиенты): «вызывайте через Gateway, не напрямую»
  • Вариант B (изменение роутера) — отклонён: обусловливает риск ломки фронта без выгоды

Решение по URL-выравниванию

Gateway rewrite (Вариант A). Внутренний роутер router.go остаётся на /v1/..., наружу торчит только /api/v1/gensvc/.... Зафиксировано 2026-05-07.

5. Обновить api-spec.yaml

  • Изменить servers[].url — добавить prod-URL через Gateway ({gateway}/api/v1/gensvc)
  • Обновить security scheme: добавить описание introspection (сейчас описан только JWKS/HS256)
  • Синхронизировать пути в спеке с выбранным вариантом URL (A или B из п.4)

6. Обновить фронт (если есть standalone UI)

  • Проверить — есть ли standalone фронтенд Photo Studio (ai-photo-studio-frontend или аналог)
  • Если есть — обновить базовый URL с https://ai-photo-studio.nirbi.ru/v1 на {gateway}/api/v1/gensvc
  • Добавить redirect на ERP login ({erp-admin}/login) если нет валидного JWT-токена

Порядок деплоя

  1. User Service деплоим с новыми ключами gensvc.* в каталоге
  2. Выпускаем service-token (см. Auth Service)
  3. Деплоим Photo Studio Service с AUTH_MODE=introspection
  4. Проверяем: POST /internal/auth/validate возвращает правильные permissions
  5. Smoke test: создать задание с тестовым токеном пользователя с gensvc.*

Связанные файлы в репозитории

ФайлЧто менять
internal/auth/claims.goДобавить поле Scope в struct Claims
internal/auth/jwks.goОставить как есть (legacy JWKS для dev/fallback)
internal/auth/introspection.goСоздать новый — IntrospectionValidator
internal/httpserver/router.goОпционально: URL-prefix если выбран Вариант B
docs/api-spec.yamlОбновить servers + security description
.env.exampleДобавить AUTH_MODE, AUTH_SERVICE_URL, AUTH_SERVICE_TOKEN

Сделано (2026-05-08)

Помимо плана выше реализовано в ходе интеграции:

Auth (см. ADR-021)

  • optional iss/aud — env JWT_ISSUER/JWT_AUDIENCE enforce-ятся только если непустые
  • UserInfoFetcher — обогащение Claims через GET /api/v1/auth/me после валидации подписи (in-process кеш, 1024 entries cap, TTL 60 сек)
  • RequirePermission middleware подключён ко всем чувствительным роутам

Polza (см. ADR-022)

  • async: true в payload POST /v1/media — устранён connection reset
  • Polling GET /v1/media/{id} каждые 3 сек, deadline 15 мин
  • isFallbackableError классификатор — fallback только на Polza-side failures
  • WORKER_CONCURRENCY=5, WORKER_JOB_TIMEOUT_SEC=1080, WORKER_MAX_RETRIES=1
  • User-friendly error message при таймауте

Безопасность (после code review)

  • StreamJob — owner check (IDOR fix)
  • MaxBytesReader на всех multipart endpoints
  • InsertWithQuota — атомарная вставка user-preset с проверкой 50-лимита
  • validateSizeCount — убрана мёртвая ветка
  • SetWriteDeadline(0) на SSE-стриме (раньше резалось через 120с)
  • Worker panic-guard на WORKER_MAX_RETRIES<1

Roadmap (после получения service-token от ERP)

  • IntrospectionValidator (см. п. 1-3 выше)
  • Gateway rewrite в проде (см. п. 4)
  • Reconciliation worker для Polza > 15 мин: сохранять polza_task_id в БД, фоновый цикл подтягивает результаты после polling-таймаута. См. ADR-022 § Roadmap.
  • Provider abstraction — единый Provider interface, переключатель AI_PROVIDER для A/B на fal.ai / OpenRouter / прямой OpenAI

Связанные контракты

Ссылки