Skip to content

Доступ ​

Кто может вызывать API и как фронтенд это доказывает. Три ситуации в том порядке, в каком вы с ними встретитесь.

1. Браузер на сайте (куки + CSRF) ​

По умолчанию. Тема или любая страница на домене вашего сайта входит в систему, и Pano ставит куки. Куки Pano привязаны только к хосту и имеют SameSite=Lax.

Каждая запись (POST, PUT, PATCH, DELETE) вошедшей cookie-сессии должна нести токен CSRF:

sh
curl -s -b jar -c jar http://localhost:8088/api/v1/auth/csrf
# {"csrfToken":"..."}
js
await fetch('/api/plugins/pano-plugin-market/cart/items', {
  method: 'POST',
  credentials: 'include',
  headers: { 'content-type': 'application/json', 'X-CSRF-Token': token },
  body: JSON.stringify({ productId: 1 })
});

Отсутствующий или неверный токен даёт 403 INVALID_CSRF_TOKEN. @panomc/sdk и типизированный клиент получают и отправляют токен за вас. Если никто не вошёл, GET /auth/csrf отвечает 401: это не ошибка, просто сначала войдите.

2. Браузер на другом адресе (разрешённые источники) ​

Страница на https://play.example.com вызывает Pano на https://example.com: добавьте источник страницы в панели в Внешний вид -> Темы -> кнопка шестерёнки (Настройки показа сайта): включите «Разрешить другим веб-сайтам доступ к этому Pano», откроется список «Веб-сайты, которым разрешён доступ к этому Pano» (или PUT /api/v1/panel/frontend/origins, не больше 20).

  • Формат scheme://host[:port], без пути и без подстановочных знаков. https, если это не localhost и не IP.
  • Он должен иметь тот же регистрируемый домен, что и website-url. Другой домен отклоняется (ORIGIN_DIFFERENT_SITE): используйте ключ фронтенда.
  • Разрешённые источники получают заголовки CORS (credentials: 'include', никогда *). Preflight отвечает 204.
  • Браузеры могут отправлять content-type, accept, x-csrf-token, x-requested-with. Заголовок Authorization из браузера не отправляется.
  • Любой другой источник не получает заголовков CORS на чтение и получает 403 ORIGIN_NOT_ALLOWED на запись.

3. Сервер (ключ фронтенда + токен сессии) ​

BFF, собственный фронтенд или любой сервер, который вызывает Pano от имени многих посетителей, использует ключ фронтенда (в панели: «Ключ подключения сайта»). Создайте его в Внешний вид -> Темы -> кнопка шестерёнки (Настройки показа сайта) -> Ключи подключения сайта -> Настроить. Он показывается один раз вместе с двумя строками .env:

sh
PANO_API_URL=https://example.com/api
PANO_FRONTEND_KEY=pfk_...
ЗаголовокЗначение
X-Pano-Frontend-Key: pfk_...Определяет ваш сервер. Неверный ключ: 401 INVALID_FRONTEND_KEY.
X-Pano-Client-Ip: 203.0.113.9Адрес посетителя. Учитывается только с действительным ключом; иначе 400 INVALID_CLIENT_IP.
Authorization: Bearer <sessionToken>Сессия посетителя, из ответа на вход.

Ключи работают только пока режим фронтенда не «Тема» (строка «Ключи подключения сайта» не показывается, пока выбрано «Тема»): в режиме «Тема» ключ создать нельзя, а запрос с сохранённым ключом получает 403 FRONTEND_ACCESS_DISABLED. Сохранённые ключи снова работают в режимах CUSTOM_APP, EXTERNAL и NONE. Разрешённые источники работают в любом режиме.

Ключ пропускает проверку источника и имеет собственные корзины ограничения частоты на каждый адрес посетителя. Правила пароля, капчи и 2FA работают без изменений.

Вход с ключом отвечает токеном в теле вместо куки:

sh
curl -s http://localhost:8088/api/v1/auth/login \
  -H 'X-Pano-Frontend-Key: pfk_...' -H 'X-Pano-Client-Ip: 203.0.113.9' \
  -H 'content-type: application/json' \
  -d '{"usernameOrEmail":"steve","password":"secret"}'
# {"sessionToken":"eyJ...","expiresAt":1790000000000}

Дальше отправляйте этот токен как Bearer. Токен CSRF не нужен. Храните его в HttpOnly-куки вашего сайта.

Токен это сессия сайта: он никогда не работает с API панели (403 SITE_TOKEN_NOT_ALLOWED) и никогда как куки. Выход, бан или смена пароля завершают его, как любую сессию.

Билет WebSocket ​

Браузеры не могут задать заголовки для WebSocket. Запросите одноразовый билет (30 секунд) и поместите его в URL:

sh
curl -s -X POST http://localhost:8088/api/v1/auth/ws-ticket -b jar -H 'X-CSRF-Token: ...'
# {"ticket":"...","expiresIn":30}

Откройте сокет с ?ticket=<ticket>. Использованный или просроченный билет даёт 401 INVALID_WS_TICKET. BFF получает билет по Bearer-токену посетителя и передаёт его браузеру.

За обратным прокси ​

Pano берёт адрес посетителя из X-Forwarded-For только от доверенного узла (server.trusted-proxies, а также loopback и частные адреса). Если все посетители выглядят как один адрес, панель показывает баннер со строкой, которую нужно добавить.