Auth Service — BR 1.4.4

Репо: erp-auth-service Контракт: API + Data Model

Зависимости

Требуется задеплоенный User Service с новыми endpoint’ами /internal/users/{id}/scope и /franchises/{id}.


Фаза 1 — JWT payload

  • В JwtService.generateAccessToken — убрать claim’ы role, store_ids, legal_entity_id. Оставить только sub, franchise_id, role_ids, iat, exp
  • В JwtService.parse / JwtUser — убрать соответствующие поля. Старые JWT с этими полями парсятся: поля просто игнорируются (backward-compat)
  • Обновить DTO JwtUser / UserPrincipal

Фаза 2 — UserScopeCache

  • Новый DTO CachedScope { type: String, legalEntityIds?: List<UUID>, storeIds?: List<UUID> } — Jackson-сериализуемый
  • Новый service UserScopeCache:
    • getOrLoad(userId): CachedScope — cache-aside. Redis key user_scope:{user_id}, TTL из config auth.scope-cache.ttl-seconds: 60
    • loadAndCache(userId) — fetch из UserServiceClient.getScope(userId)
    • evict(userId)
  • TTL в application.yml — добавить

Фаза 3 — UserServiceClient

  • Новый метод getScope(userId): CachedScope — вызывает GET /internal/users/{id}/scope
  • Новый метод getFranchise(franchiseId): FranchiseDto — вызывает GET /api/v1/franchises/{id} (использует service token?) или отдельный internal. Уточнить: нужен internal endpoint GET /internal/franchises/{id}? (если нет — можно использовать public с service-token)

Фаза 4 — Response classes

  • UserInfoResponse (для /auth/login + /auth/me):
    • Убрать: role, store_ids, legal_entity_id
    • Добавить: franchise: { id, type }, scope: { type, legal_entity_ids?, store_ids? }, оставить role_ids, permissions
  • ValidateResponse (для /internal/auth/validate):
    • Убрать role, store_ids, legal_entity_id
    • Добавить scope (+ role_ids, permissions уже были)

Фаза 5 — AuthService

  • login(email, password):
    • Валидировать через User Service (uses existing endpoint)
    • Получить scope через UserScopeCache.getOrLoad(userId)
    • Получить franchise через UserServiceClient.getFranchise(franchiseId) (либо кэш franchise_type отдельно, TTL 300s? — опционально)
    • Сложить всё в LoginResponse
  • me():
    • Аналогично login, но authenticated по JWT
  • validate(token):
    • Decode JWT → userId, role_ids
    • Получить permissions через existing UserPermissionsCache
    • Получить scope через UserScopeCache
    • Вернуть ValidateResponse

Фаза 6 — Redis session (если используется)

  • session:{user_id}:{token_hash} — убрать role, store_ids, legal_entity_id из сохраняемой JSON-структуры. Оставить { user_id, role_ids, franchise_id }

Фаза 7 — Тесты (optional)

  • UserScopeCache — hit/miss/expiry
  • ValidateResponse — поля корректные после валидации
  • Регрессия: старые JWT с role-полем не ломают парсинг

Выходные критерии

  • Все claim’ы role, store_ids, legal_entity_id убраны из JWT
  • Все ответы /auth/* и /internal/auth/validate — содержат scope + franchise.type (где уместно)
  • Docker build OK
  • Коммит + push