# Приёмка liderra.ru — ПУНКТ №25: Импер­сонизация — клиентская сторона (G7-B)

> **Для исполнителя:** формат PR1 (одобрен 20.06). Каждый шаг = `🔧 Код-факт (file:line)` · `🎬 Действие` · `📋 ОТЧЁТ (было→ожидали→стало + 📸/📝)` · `❌ Если не так`. Техника «под капотом»; владельцу — человеческие карточки.

**Цель:** показать, что когда поддержка (или ИИ) входит в кабинет клиента «помочь» — это происходит **прозрачно и с согласия**: клиенту приходит письмо с кодом-согласием, во время такого входа в его кабинете висит **красная плашка «режим поддержки»** с кнопкой **«Выйти»**, сессия сама завершается через час, и по окончании клиент получает второе письмо. Машинный ключ ИИ не пускают в деньги и админку.

**Что снимаем глазами:** **красная плашка** в кабинете, кнопка «Выйти», 2 письма клиенту. Живые 📸 — **в основном на боевом прогоне** (нужна админская сторона + реальное письмо клиенту).

**Источник сценария:** свод проверок №25 (G7-B); новая фича деплоя 19.06; карточки 9-12 эфира (заметка «на прод-прогоне»).

> ⚠️ Этот пункт **преимущественно 🟦 на живой прод-прогон**: импер­сонизация требует входа со стороны SaaS-админа + письма на почту клиента — локально полностью не воспроизвести. Клиентскую плашку и логику фиксируем по коду; демонстрацию — на боевом.

---

## 🔧 Код-факты (подтверждено чтением 20.06)

- **Контекст в /api/auth/me** — при активной сессии возвращается блок `impersonation {active:true, tenant_name, started_at, expires_at}` — [AuthController.php:140-150](../../../app/app/Http/Controllers/Api/AuthController.php#L140-L150).
- **Красная плашка клиента** — `ImpersonationSessionBanner.vue` показывается при `imp.active`: «Вы в кабинете клиента «X» — режим поддержки» + кнопка **«Выйти»** (`leave`) — [resources/js/components/admin/ImpersonationSessionBanner.vue:19-25](../../../app/resources/js/components/admin/ImpersonationSessionBanner.vue#L19-L25).
- **Поток с согласием** — admin `init {tenant_id, reason}` → 6-значный код, hash в `impersonation_tokens`, **plain-код письмом на `tenant.contact_email`**, TTL **15 мин**; admin `verify {token_id, code}` → старт; admin `end` → завершение + письмо — [ImpersonationController.php:26-33](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L26-L33).
- **Сроки** — код 15 мин ([TOKEN_TTL_MINUTES=15 :45](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L45)); сессия **авто-истекает 60 мин** ([SESSION_TTL_MINUTES=60 :49](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L49)).
- **Два письма клиенту** — код-согласие `ImpersonationCodeMail` + завершение `ImpersonationEndedMail` — [:8-9](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L8-L9).
- **Машинный ключ ИИ `lpimp_`** — доступа к биллингу/админке нет, но **коды статусов разные** (lpimp-status, сверено по коду):
  - `/api/admin/*` → **403** (`EnsureSaasAdmin` видит impersonation-маркер/`lpimp_` и abort'ит);
  - биллинг (большинство) и `/api/api-keys` → **401**, НЕ 403 (роуты несут только `auth:sanctum`, гарда `impersonation` там нет → ключ просто не аутентифицирован);
  - ⚠️ **исключение:** `/api/billing/charges` ([web.php:193](../../../app/routes/web.php#L193)) **включает** middleware `impersonation` → `lpimp_` **может ЧИТАТЬ ленту списаний** (read-only). Net-доступ к мутациям нет, но ленту видит — осознать на прогоне.
  - чужой/битый Bearer → **401**.

---

## Шаг 25-1 — Запрос входа: клиенту письмо с кодом-согласием (🟦 прод)

**🔧 Код:** [ImpersonationController init :26-28](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L26-L28); код 15 мин — [:45](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L45).
**🎬 Действие (прод):** админ запрашивает вход в кабинет клиента с причиной (reason ≥ 30 символов) → клиенту приходит письмо с 6-значным кодом.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Письмо клиенту | — | код-согласие (6 цифр, живёт 15 мин) | _(слот боевого прогона)_ | 📸 письмо клиенту |
| Без причины ≥30 | — | запрос не принимается | _(слот)_ | 📝 валидация reason |

**💡 Что внутри:** просто так «зайти под клиента» нельзя. Сотрудник указывает причину, и клиенту на почту приходит код — без этого кода вход не состоится. Это согласие клиента и его след.

---

## Шаг 25-2 — Красная плашка «режим поддержки» в кабинете клиента

**🔧 Код:** [AuthController :140-150](../../../app/app/Http/Controllers/Api/AuthController.php#L140-L150); плашка — [ImpersonationSessionBanner.vue:19-25](../../../app/resources/js/components/admin/ImpersonationSessionBanner.vue#L19-L25).
**🎬 Действие (прод):** во время активной сессии открыть кабинет клиента.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Плашка | нет | **красная**: «Вы в кабинете… — режим поддержки» | _(слот)_ | 📸 кабинет с плашкой |
| Кнопка «Выйти» | — | присутствует и работает | _(слот)_ | 📸 плашка с кнопкой |

**💡 Что внутри:** пока поддержка работает в кабинете клиента, наверху висит заметная красная полоса «режим поддержки» — чтобы никто не перепутал, что это не обычный вход. Рядом — кнопка «Выйти», чтобы сразу прекратить.

**❌ Если не так:** плашки нет / не красная / нет кнопки → клиент не видит, что в его кабинете кто-то от поддержки (дефект прозрачности).

---

## Шаг 25-3 — Завершение: кнопка «Выйти», авто-истечение 1 час, письмо

**🔧 Код:** `leave` — [ImpersonationSessionBanner.vue:9](../../../app/resources/js/components/admin/ImpersonationSessionBanner.vue#L9); end+письмо — [ImpersonationController :32-33](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L32-L33); сессия 60 мин — [:49](../../../app/app/Http/Controllers/Api/ImpersonationController.php#L49).
**🎬 Действие (прод):** нажать «Выйти» (или дождаться истечения часа).

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Кнопка «Выйти» | сессия идёт | сессия завершена, плашка пропала | _(слот)_ | 📸 кабинет без плашки |
| Авто-истечение | — | через 60 мин сессия закрывается сама | _(слот)_ | 📝 `session_ended_at`/expiry |
| Второе письмо | — | клиенту «сессия поддержки завершена» | _(слот)_ | 📸 письмо-завершение |

**💡 Что внутри:** сессию можно прекратить в любой момент кнопкой «Выйти», а если про неё забыли — она всё равно сама закроется через час. По окончании клиент получает письмо «поддержка завершила работу в вашем кабинете». Клиент всегда в курсе начала и конца.

---

## Шаг 25-4 — Машинный ключ ИИ не пускают в деньги/админку (🟦 прод-верификация)

**🔧 Код:** ключ `lpimp_` ограничен (по своду G7-B); чужой Bearer → 401.
**🎬 Действие (прод):** запросом с ключом `lpimp_` попробовать биллинг / `/api/api-keys` / `/api/admin/*`.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| `/api/admin/*` по `lpimp_` | — | **403** (EnsureSaasAdmin abort) | _(слот боевого прогона)_ | 📝 ответ 403 |
| Биллинг (мутации) / `/api/api-keys` по `lpimp_` | — | **401** (не 403 — нет impersonation-гарда) | _(слот)_ | 📝 ответ 401 |
| `/api/billing/charges` по `lpimp_` | — | **читается** (read-only, гард impersonation есть) | _(слот)_ | 📝 ответ 200, лента списаний |
| Чужой/битый Bearer | — | **401** | _(слот)_ | 📝 ответ 401 |

**💡 Что внутри:** у ИИ-помощника свой машинный ключ, и ему намеренно закрыт доступ к деньгам клиента и к админским функциям — он может только то, что положено в режиме помощи. Чужой ключ просто не пускают.

**❌ Если не так:** `lpimp_` лезет в биллинг/админку → дыра доступа (СТОП).

---

## Verification №25 (выход из пункта)
- [ ] 🟦 (прод) Запрос входа с причиной → клиенту письмо с кодом (15 мин).
- [ ] 🟦/📸 Красная плашка «режим поддержки» + кнопка «Выйти» в кабинете.
- [ ] 🟦 «Выйти» завершает; авто-истечение 60 мин; второе письмо «завершено».
- [ ] 🟦 `lpimp_` → 403 на биллинг/админку; чужой Bearer → 401.

## 📸 vs 📝 в этом пункте
- **📸 Скриншот (на проде):** красная плашка с кнопкой «Выйти», 2 письма клиенту.
- **📝 Текст:** контекст `/api/auth/me`, сроки 15/60 мин, 403/401 по ключу — текстом.

## Грабли
- Пункт **в основном прод-only** — нужна админская сторона + почта клиента; локально воспроизводится частично (плашку можно показать, если удастся завести активную сессию).
- 2 письма обязательны (начало-согласие + конец) — проверить оба.
- Связка с №16 (изоляция) и №26 (ключи): `lpimp_` ≠ `lpkapi_` — разные ключи, разные права.
