fix and update

This commit is contained in:
lendry
2026-07-01 17:00:55 +03:00
parent 607397fcf3
commit 0c3c6d6d82
8 changed files with 323 additions and 184 deletions

View File

@@ -964,7 +964,7 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
blocks: [
{
type: 'paragraph',
text: 'Для входа в один клик на сторонних сайтах (FedCM + виджет sso-widget.js) см. отдельный раздел документации.'
text: 'Для входа в один клик на сторонних сайтах (FedCM + виджет sso-widget.js) см. раздел «One Tap Login»: split-domain, branding, fields API, popup fallback.'
},
{
type: 'list',
@@ -1004,49 +1004,111 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
blocks: [
{
type: 'paragraph',
text: 'One Tap Login позволяет пользователям вашего сайта войти через Lendry ID без полного редиректа на страницу авторизации. Поддерживаются два режима: Federated Credential Management (FedCM) в современных браузерах и виджет sso-widget.js с popup-окном для остальных.'
text: 'One Tap Login позволяет пользователям вашего сайта войти через IdP без полного редиректа на страницу авторизации. Поддерживаются два режима: Federated Credential Management (FedCM) в Chrome/Edge и виджет sso-widget.js с popup-окном для остальных браузеров.'
},
{
type: 'callout',
variant: 'info',
title: 'Предварительные требования',
text: 'Создайте OAuth-приложение в админ-панели (RBAC → OAuth приложения): укажите redirect_uri вашего сайта, scopes openid profile email и сохраните client_id. Подробнее — раздел «OAuth 2.0 / OIDC».'
text: 'Создайте OAuth-приложение в админ-панели (RBAC → OAuth приложения): укажите redirect_uri вашего сайта, scopes openid profile email и сохраните client_id. В системных настройках задайте PUBLIC_API_URL, PUBLIC_FRONTEND_URL и PROJECT_NAME. Подробнее — раздел «OAuth 2.0 / OIDC».'
},
{
type: 'list',
items: [
'FedCM — браузер показывает нативный диалог «Войти как …», если пользователь уже залогинен на домене IdP',
'Fallback — скрипт sso-widget.js рисует плашку «Войти через …» в правом верхнем углу и открывает popup с OAuth',
'Оба режима возвращают OIDC id_token (FedCM) или authorization code / токены (popup) — проверяйте на backend'
'FedCM — браузер показывает нативный диалог «Войти в … через {PROJECT_NAME}», если пользователь уже залогинен на IdP',
'Fallback — sso-widget.js рисует плашку «Войти через …» и открывает popup OAuth (display=popup)',
'FedCM возвращает OIDC id_token; popup — authorization code или токены через postMessage. Проверяйте на backend'
]
}
]
},
{
id: 'fedcm-architecture',
title: 'Архитектура FedCM (split-domain)',
blocks: [
{
type: 'paragraph',
text: 'FedCM endpoints размещаются на PUBLIC_API_URL (issuer), например https://api.idpmvk.lpr. UI авторизации — на PUBLIC_FRONTEND_URL, например https://sso.idpmvk.lpr. Виджет sso-widget.js подключается с frontend-домена, а configURL указывает на API-домен.'
},
{
type: 'list',
items: [
'configURL для navigator.credentials.get() — всегда {PUBLIC_API_URL}/fedcm/config.json, не путь через frontend',
'login_url в config.json обязан быть same-origin с config.json (требование Chrome). У нас: {PUBLIC_API_URL}/auth/login — nginx проксирует UI с frontend',
'Cookie lendry_fedcm_sess в production: Domain=.{корневой-домен}, Secure, SameSite=None — чтобы FedCM работал между api.* и sso.*',
'Манифест /.well-known/web-identity может отдаваться с apex-домена; provider_urls указывает на config.json API-домена'
]
},
{
type: 'callout',
variant: 'warning',
title: 'Типичные ошибки',
text: 'NetworkError / invalid login_url — login_url на другом origin, чем config.json. 401 на accounts — нет cookie или пользователь не залогинен на IdP. Disclosure показывает только «имя» — RP не передал fields в navigator.credentials.get().'
}
]
},
{
id: 'fedcm-endpoints',
title: 'FedCM endpoints',
blocks: [
{
type: 'paragraph',
text: 'Endpoints размещены на PUBLIC_API_URL (issuer). Локально: http://localhost:3000. При same-origin деплое: https://ваш-домен/idp-api. CORS настроен с credentials: true для запросов с сайтов-клиентов.'
text: 'Все URL ниже — относительно PUBLIC_API_URL (issuer). CORS настроен с credentials: true для запросов FedCM с сайтов-клиентов. Запросы config/accounts/id_assertion должны содержать Sec-Fetch-Dest: webidentity (Chrome добавляет автоматически).'
},
{
type: 'table',
headers: ['Endpoint', 'Метод', 'Описание'],
rows: [
['{PUBLIC_API_URL}/.well-known/web-identity', 'GET', 'Манифест FedCM → provider_urls'],
['{PUBLIC_API_URL}/fedcm/config.json', 'GET', 'Конфигурация IdP (accounts, id_assertion, branding)'],
['{PUBLIC_API_URL}/.well-known/web-identity', 'GET', 'Манифест FedCM → provider_urls, accounts_endpoint, login_url'],
['{PUBLIC_API_URL}/fedcm/config.json', 'GET', 'Конфигурация IdP: endpoints, login_url, branding (name, icons, colors)'],
['{PUBLIC_API_URL}/fedcm/discover.json', 'GET', 'Публичный discovery для sso-widget.js (configUrl, suggestedFields, projectName)'],
['{PUBLIC_API_URL}/fedcm/accounts', 'GET', 'Список аккаунтов по cookie lendry_fedcm_sess'],
['{PUBLIC_API_URL}/fedcm/id_assertion', 'POST', 'Выдача id_token для client_id + account_id'],
['{PUBLIC_API_URL}/fedcm/client_metadata', 'GET', '?client_id=… — privacy/terms для UI FedCM'],
['{PUBLIC_API_URL}/fedcm/session/sync', 'POST', 'Установить FedCM cookie по Bearer access token']
['{PUBLIC_API_URL}/fedcm/session/sync', 'POST/GET', 'Установить FedCM cookie по Bearer access token'],
['{PUBLIC_API_URL}/fedcm/login-status', 'GET', 'HTML-мост Set-Login на API origin (origin login_url)']
]
},
{
type: 'callout',
variant: 'info',
title: 'Branding в config.json',
text: 'branding.name берётся из PROJECT_NAME (админка). Иконки: {PUBLIC_FRONTEND_URL}/icon.svg (40px) и favicon API-домена. В диалоге Chrome отображается название проекта вместо технического домена IdP.'
},
{
type: 'callout',
variant: 'warning',
title: 'FedCM cookie',
text: 'FedCM использует HttpOnly cookie lendry_fedcm_sess на домене IdP (SameSite=None; Secure в production). Cookie устанавливается при входе на IdP или через POST /fedcm/session/sync. localStorage JWT с сайта клиента для FedCM не подходит.'
text: 'HttpOnly cookie lendry_fedcm_sess на домене IdP. Устанавливается при входе на IdP или через /fedcm/session/sync. JWT из localStorage сайта-клиента для FedCM не подходит — нужна сессия на IdP.'
}
]
},
{
id: 'fedcm-accounts-fields',
title: 'Данные аккаунта и disclosure',
blocks: [
{
type: 'paragraph',
text: 'GET /fedcm/accounts возвращает массив accounts с полями id, name, given_name, email, picture, tel (если есть у пользователя). picture — публичный URL аватара (legacy avatarUrl или временная ссылка /media/stream/{token}).'
},
{
type: 'paragraph',
text: 'Текст disclosure («какие данные передаются сайту») контролирует сайт-клиент (RP), а не IdP. RP должен указать fields в navigator.credentials.get():'
},
{
type: 'code',
language: 'javascript',
title: 'Fields API (Chrome 132+, tel — Chrome 141+)',
code: `fields: ['name', 'email', 'picture', 'tel']`
},
{
type: 'list',
items: [
'sso-widget.js передаёт fields автоматически',
'discover.json содержит suggestedFields для интеграторов',
'Без fields Chrome по умолчанию показывает disclosure только для имени',
'id_token после входа содержит claims OAuth scopes (openid profile email)'
]
}
]
},
@@ -1056,16 +1118,16 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
blocks: [
{
type: 'paragraph',
text: 'Скрипт размещён на frontend IdP по адресу {PUBLIC_FRONTEND_URL}/sso-widget.js. Достаточно одного тега script — инициализация выполняется автоматически при загрузке страницы.'
text: 'Скрипт размещён на frontend IdP: {PUBLIC_FRONTEND_URL}/sso-widget.js. Достаточно одного тега script — инициализация выполняется автоматически. Виджет сначала запрашивает /fedcm/discover.json на API-домене, затем вызывает FedCM или popup.'
},
{
type: 'table',
headers: ['Атрибут data-*', 'Обязательный', 'Описание'],
rows: [
['data-client-id', 'Да', 'client_id OAuth-приложения из админки'],
['data-idp-url', 'Нет', 'PUBLIC_API_URL; по умолчанию origin скрипта + /idp-api'],
['data-idp-frontend-url', 'Нет', 'URL frontend IdP для popup; по умолчанию origin скрипта'],
['data-provider-name', 'Нет', 'Название в плашке (по умолчанию MVK ID)'],
['data-idp-url', 'Да*', 'PUBLIC_API_URL (issuer). Обязателен, если скрипт не на том же домене'],
['data-idp-frontend-url', 'Нет', 'PUBLIC_FRONTEND_URL для popup; по умолчанию origin скрипта'],
['data-provider-name', 'Нет', 'Название в плашке fallback (по умолчанию PROJECT_NAME)'],
['data-redirect-uri', 'Нет', 'redirect_uri OAuth; по умолчанию origin + /auth/callback'],
['data-scope', 'Нет', 'Scopes OAuth (по умолчанию openid profile email)'],
['data-on-success', 'Нет', 'Имя глобальной функции-callback'],
@@ -1076,7 +1138,7 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
type: 'callout',
variant: 'tip',
title: 'Логика выбора режима',
text: 'Если браузер поддерживает IdentityCredential — скрипт вызывает FedCM. Если нет — показывается плашка в правом верхнем углу; по клику открывается popup OAuth (параметр display=popup).'
text: 'Если браузер поддерживает IdentityCredential — виджет вызывает FedCM с fields: name, email, picture, tel. Иначе — плашка в правом верхнем углу; по клику popup OAuth.'
}
]
},
@@ -1086,14 +1148,14 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
blocks: [
{
type: 'paragraph',
text: 'Интерактивный конструктор позволяет настроить кнопку входа под внешний вид вашего сайта. Выберите параметры — справа обновится живое превью, а слева сформируется готовый код виджета. Скопируйте его и вставьте на свою страницу: атрибут data-button-container указывает, в какой элемент встроить кнопку.'
text: 'Интерактивный конструктор позволяет настроить кнопку входа под внешний вид вашего сайта. URL в примерах подставляются из PUBLIC_API_URL и PUBLIC_FRONTEND_URL текущего IdP.'
},
{ type: 'one-tap-builder' },
{
type: 'callout',
variant: 'tip',
title: 'Как это работает',
text: 'Кнопка рендерится скриптом sso-widget.js внутри контейнера с указанным id. Параметры размера, темы, вида, радиуса, иконки и CSS-цветов передаются через data-button-* атрибуты. Клик по превью открывает реальный popup авторизации для проверки.'
text: 'Кнопка рендерится sso-widget.js внутри контейнера с указанным id. Клик по превью открывает реальный popup авторизации для проверки.'
}
]
},
@@ -1103,7 +1165,7 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
blocks: [
{
type: 'paragraph',
text: 'Если вы не используете sso-widget.js, можно вызвать FedCM API напрямую. configURL должен указывать на /fedcm/config.json провайдера.'
text: 'Если вы не используете sso-widget.js, вызовите FedCM API напрямую. configURL — {PUBLIC_API_URL}/fedcm/config.json. Пользователь должен быть залогинен на IdP (cookie lendry_fedcm_sess).'
},
{
type: 'code',
@@ -1112,8 +1174,9 @@ $oidc->authenticate(); // client_id, redirect_uri, response_type=code — авт
code: `const credential = await navigator.credentials.get({
identity: {
providers: [{
configURL: 'https://id.lendry.ru/idp-api/fedcm/config.json',
clientId: 'YOUR_CLIENT_ID'
configURL: '{PUBLIC_API_URL}/fedcm/config.json',
clientId: 'YOUR_CLIENT_ID',
fields: ['name', 'email', 'picture', 'tel']
}]
},
mediation: 'optional'
@@ -1127,7 +1190,8 @@ const idToken = credential?.token;
items: [
'mediation: "optional" — показать диалог только при наличии сессии IdP',
'mediation: "required" — всегда показывать UI выбора аккаунта',
'mediation: "silent" — без UI; вернёт ошибку, если пользователь не залогинен'
'mediation: "silent" — без UI; ошибка, если пользователь не залогинен',
'data-idp-url в виджете и configURL в ручной интеграции — публичный API-домен, доступный из браузера пользователя'
]
}
]
@@ -1138,7 +1202,7 @@ const idToken = credential?.token;
blocks: [
{
type: 'paragraph',
text: 'При клике на виджет открывается popup с OAuth authorize. После успешного входа IdP отправляет результат родительскому окну через window.postMessage с типом lendry-sso-onetap.'
text: 'Если FedCM недоступен, виджет показывает плашку и по клику открывает popup с OAuth authorize на {PUBLIC_FRONTEND_URL}/auth/oauth/authorize?display=popup. После успешного входа IdP отправляет результат родительскому окну через window.postMessage.'
},
{
type: 'code',
@@ -1146,16 +1210,16 @@ const idToken = credential?.token;
title: 'Обработка на сайте клиента',
code: `window.addEventListener('message', (event) => {
if (event.data?.type !== 'lendry-sso-onetap') return;
// Проверяйте event.origin — домен вашего IdP
// Проверяйте event.origin — PUBLIC_FRONTEND_URL вашего IdP
const { token, idToken, accessToken, code, method } = event.data;
console.log('Вход через', method);
console.log('Вход через', method); // 'fedcm' | 'popup'
});`
},
{
type: 'callout',
variant: 'info',
title: 'redirect_uri',
text: 'redirect_uri должен быть зарегистрирован у OAuth-клиента. Для SPA часто используют https://your-app.com/auth/callback — на этой странице backend обменивает code на токены, либо popup передаёт токен через postMessage.'
text: 'redirect_uri должен быть зарегистрирован у OAuth-клиента. Для SPA часто используют https://your-app.com/auth/callback — backend обменивает code на токены, либо popup передаёт токен через postMessage.'
}
]
},
@@ -1165,7 +1229,7 @@ const idToken = credential?.token;
blocks: [
{
type: 'paragraph',
text: 'FedCM возвращает id_token (JWT). Проверьте подпись (HS256, секрет JWT_ACCESS_SECRET IdP), issuer = PUBLIC_API_URL и aud = ваш client_id. Альтернатива — обмен authorization code через POST /oauth/token и запрос GET /oauth/userinfo.'
text: 'FedCM возвращает id_token (JWT). Проверьте подпись (HS256, секрет JWT_ACCESS_SECRET IdP), issuer = PUBLIC_API_URL и aud = ваш client_id. Альтернатива — обмен authorization code через POST /oauth/token и GET /oauth/userinfo.'
},
{
type: 'list',
@@ -1178,30 +1242,37 @@ const idToken = credential?.token;
]
},
{
id: 'examples',
title: 'Примеры интеграции',
blocks: [{ type: 'one-tap-examples' }]
},
{
id: 'local-dev',
title: 'Локальная разработка',
id: 'testing',
title: 'Проверка интеграции',
blocks: [
{
type: 'table',
headers: ['Сервис', 'URL'],
rows: [
['API (FedCM endpoints)', 'http://localhost:3000'],
['Frontend (sso-widget.js)', 'http://localhost:3002/sso-widget.js'],
['Документация', 'http://localhost:3003/docs/one-tap-login']
type: 'list',
items: [
'Войдите на IdP ({PUBLIC_FRONTEND_URL}) в том же браузере, где тестируете сайт-клиент',
'Убедитесь, что PUBLIC_API_URL в настройках = домен, который видит браузер (не внутренний Docker-хост)',
'Проверьте login_url в config.json: curl -s {PUBLIC_API_URL}/fedcm/config.json -H "Sec-Fetch-Dest: webidentity" | jq .login_url',
'ONE_TAP_ENABLED=true в системных настройках (по умолчанию включено)',
'При смене FEDCM_COOKIE_DOMAIN пользователям может потребоваться перелогин на IdP'
]
},
{
type: 'callout',
variant: 'tip',
title: 'Тест FedCM локально',
text: 'FedCM cookie в dev использует SameSite=Lax (без Secure). Войдите на http://localhost:3002, затем откройте тестовую страницу клиента с подключённым виджетом. Chrome может требовать флаг chrome://flags/#identity-credentials-api или HTTPS для FedCM — в этом случае проверяйте popup fallback.'
title: 'Диагностика в админке',
text: 'В карточке OAuth-приложения (RBAC → OAuth приложения → One Tap Login) отображаются актуальные FedCM URL, примеры кода и чеклист проверки.'
}
]
},
{
id: 'examples',
title: 'Примеры интеграции',
blocks: [
{
type: 'paragraph',
text: 'Примеры ниже автоматически подставляют PUBLIC_API_URL, PUBLIC_FRONTEND_URL и PROJECT_NAME из настроек текущего IdP.'
},
{ type: 'one-tap-examples' }
]
}
]
},