# Фото сотрудника из Nozhmaster SSO

Этот контракт обязателен для Bark-приложений с центральной авторизацией. Он
меняет только источник изображения внутри уже существующего компонента
`Avatar`: размеры, рамка, форма, расположение рядом с именем и fallback-стиль
остаются такими, как их определяет приложение.

Если существующего `Avatar` нет (новый продукт), используется `.bark-avatar` и
размещение из `docs/COMPONENT_RULES.md` §«Шапка страницы и идентичность»:
блок `.bark-sidebar__user` в футере сайдбара — аватар 40px + имя + email +
icon-only `Выйти`; это единственная точка идентичности оболочки (`barkone` —
задокументированное легаси-исключение с аватаром в шапке). Имя и email берутся
из той же текущей SSO-сессии, что и `picture`, и не сохраняются в локальный
профиль. Инициалы fallback всегда считает `initialsFromName()` из
`templates/sso-avatar.adapter.js` — первая буква первого и последнего слова,
верхний регистр («Владимир Шурыгин» → «ВШ»); не изобретать свою формулу.

## Loading при входе

Если приложение использует SSO, его существующая кнопка входа обязана сразу
после нажатия показать Bark spinner 18px и текст `Входим…`. Кнопка получает
`disabled` и `aria-busy="true"`; состояние сохраняется до перехода на SSO или
ошибки. При ошибке исходная кнопка восстанавливается и позволяет повторить вход.

Для redirect-flow использовать `startButtonLoading()` из
`templates/loader.snippet.js`: успешный запуск не снимает loading перед уходом
страницы. После возврата с SSO callback/bootstrap показывает full-screen brand
loader `lg`. Не показывать button spinner и full-screen overlay одновременно и
не создавать вторую login-кнопку ради этого состояния.

## Источник данных

- После `check_session` брать URL только из `response.user.picture`.
- При использовании `client_sso_v2` брать то же значение из
  `$user['picture']`.
- Непустой URL передавать напрямую в `src` внутреннего `<img>` существующего
  `Avatar`.
- Считать URL непрозрачным и подписанным. Тип изображения нельзя определять по
  пути или расширению: `/photo.php?t=...` может вернуть `image/jpeg` или
  `image/svg+xml`.
- При каждом восстановлении/обновлении SSO-сессии использовать актуальное
  значение `picture`. Не сохранять подписанный URL в БД, `localStorage`,
  постоянном профиле пользователя или долгоживущем кеше.

JavaScript:

```js
const response = await checkSession();
const picture = response.user.picture;
```

PHP с `client_sso_v2`:

```php
$picture = $user['picture'] ?? null;
if (!is_string($picture) || trim($picture) === '') {
    $picture = null;
}

// Передать $picture в существующий Avatar. Не загружать файл на сервер.
```

При server-side rendering URL нужно только корректно экранировать для HTML.
Нельзя переписывать, скачивать или перекодировать его.

## Существующий Avatar

Состояния компонента:

1. `picture` непустой и этот URL ещё не завершился ошибкой: показать `<img>`.
2. `picture` пустой: сразу показать существующий fallback с инициалами.
3. `img` вызвал `error`: скрыть изображение и показать те же инициалы.
4. SSO вернул новый подписанный URL: снова попробовать изображение. Ошибка
   старого URL не должна навсегда отключать фото сотрудника.

Обязательные атрибуты внутреннего изображения:

```html
<img
  src="{response.user.picture}"
  loading="lazy"
  decoding="async"
  referrerpolicy="no-referrer"
  alt=""
>
```

Пустой `alt` используется, когда имя сотрудника уже подписано рядом с Avatar и
фотография декоративно дублирует эту информацию. Если Avatar стоит без имени,
локальный компонент должен задать осмысленный `alt`.

Готовый адаптер: `templates/sso-avatar.adapter.js`. Он возвращает `src`,
инициалы и свойства внутреннего `img`, но не создаёт новый компонент и не
добавляет CSS. Названия пропсов нужно сопоставить с API существующего `Avatar`.

Пример интеграции с компонентом, который принимает `src`, `fallback` и
`imgProps`:

```jsx
const [failedPicture, setFailedPicture] = useState(null);
const avatar = createSsoAvatarProps(response.user, {
  initials,
  failedPicture,
  onPictureError: setFailedPicture,
});

<Avatar
  src={avatar.src ?? undefined}
  fallback={avatar.fallback}
  imgProps={avatar.imgProps}
/>
```

Если локальный `Avatar` называет эти свойства иначе, адаптируется только mapping;
контракт `src`, fallback и атрибутов изображения остаётся тем же.

## CSP

Если приложение использует Content Security Policy, в существующую директиву
`img-src` нужно добавить `https://s-s.nozhmaster.ru` и сохранить уже разрешённый
Google image origin. Не заменять всю директиву новой строкой и не расширять
остальные источники.

Например, если приложение уже разрешает `https://lh3.googleusercontent.com`:

```text
img-src 'self' https://lh3.googleusercontent.com https://s-s.nozhmaster.ru;
```

Если Google-фотографии приходят с другого уже зафиксированного origin, сохранить
именно его и рядом добавить `https://s-s.nozhmaster.ru`.

## Запрещено

- вызывать `team.nozhmaster.ru/api/sso/photo` напрямую;
- использовать, добавлять в `.env` или запрашивать `SSO_PHOTO_TOKEN`;
- загружать изображение приложением перед показом;
- проксировать его через backend/CDN приложения;
- преобразовывать изображение в base64 или `data:` URL;
- определять JPEG/SVG по расширению или query-параметрам;
- логировать полный подписанный URL или сохранять его как постоянные данные;
- создавать отдельный SSO-avatar со своей геометрией вместо локального `Avatar`.

## Проверка

Обязательные тестовые сценарии:

- подписанный URL, отдающий `image/jpeg`, передаётся в `src` без изменений;
- подписанный URL той же формы, отдающий `image/svg+xml`, обрабатывается так же;
- пустой `picture` показывает инициалы без пустого/сломанного `<img>`;
- ошибка загрузки переключает текущий URL на fallback;
- новый URL из обновлённой SSO-сессии снова отображается после ошибки старого.
