174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
import type { OAuthExample } from '@/lib/oauth-examples';
|
||
|
||
export interface OneTapUrls {
|
||
apiBase: string;
|
||
frontendBase: string;
|
||
widgetUrl: string;
|
||
fedcmConfigUrl: string;
|
||
fedcmDiscoverUrl: string;
|
||
webIdentityUrl: string;
|
||
projectName: string;
|
||
}
|
||
|
||
export function buildOneTapUrls(
|
||
apiBase: string,
|
||
frontendBase: string,
|
||
projectName = 'MVK ID'
|
||
): OneTapUrls {
|
||
const base = apiBase.replace(/\/+$/, '');
|
||
const front = frontendBase.replace(/\/+$/, '');
|
||
return {
|
||
apiBase: base,
|
||
frontendBase: front,
|
||
widgetUrl: `${front}/sso-widget.js`,
|
||
fedcmConfigUrl: `${base}/fedcm/config.json`,
|
||
fedcmDiscoverUrl: `${base}/fedcm/discover.json`,
|
||
webIdentityUrl: `${base}/.well-known/web-identity`,
|
||
projectName
|
||
};
|
||
}
|
||
|
||
export function buildOneTapExamples(urls: OneTapUrls, clientIdPlaceholder = 'YOUR_CLIENT_ID'): OAuthExample[] {
|
||
const { apiBase, frontendBase, widgetUrl, fedcmConfigUrl, fedcmDiscoverUrl, webIdentityUrl, projectName } = urls;
|
||
const redirectUri = 'https://app.example.com/auth/callback';
|
||
|
||
return [
|
||
{
|
||
id: 'widget-script',
|
||
label: 'Виджет (script tag)',
|
||
language: 'html',
|
||
code: `<!-- Подключите на любой странице вашего сайта -->
|
||
<script
|
||
src="${widgetUrl}"
|
||
data-client-id="${clientIdPlaceholder}"
|
||
data-idp-url="${apiBase}"
|
||
data-idp-frontend-url="${frontendBase}"
|
||
data-provider-name="${projectName}"
|
||
data-redirect-uri="${redirectUri}"
|
||
data-on-success="handleLendryLogin"
|
||
></script>
|
||
|
||
<script>
|
||
function handleLendryLogin(payload) {
|
||
// payload.token — id_token (FedCM) или токен из popup
|
||
// payload.method — 'fedcm' | 'popup'
|
||
console.log('Вход через', payload.method, payload.token);
|
||
// Отправьте token на ваш backend для проверки и создания сессии
|
||
}
|
||
</script>`
|
||
},
|
||
{
|
||
id: 'fedcm-native',
|
||
label: 'FedCM (нативный API)',
|
||
language: 'javascript',
|
||
code: `// Работает в Chrome/Edge 132+ (поля name/email/picture/tel — с Chrome 141 для tel).
|
||
// Пользователь должен быть залогинен на ${frontendBase} (cookie lendry_fedcm_sess на домене IdP).
|
||
|
||
async function loginWithFedCM() {
|
||
if (!('IdentityCredential' in window)) {
|
||
throw new Error('FedCM не поддерживается в этом браузере');
|
||
}
|
||
|
||
const credential = await navigator.credentials.get({
|
||
identity: {
|
||
providers: [{
|
||
configURL: '${fedcmConfigUrl}',
|
||
clientId: '${clientIdPlaceholder}',
|
||
// Chrome 132+: disclosure в диалоге FedCM (имя, email, аватар, телефон)
|
||
fields: ['name', 'email', 'picture', 'tel']
|
||
}]
|
||
},
|
||
mediation: 'optional'
|
||
});
|
||
|
||
if (!credential?.token) {
|
||
throw new Error('Пользователь отменил вход или сессия IdP отсутствует');
|
||
}
|
||
|
||
return credential.token; // OIDC id_token
|
||
}
|
||
|
||
loginWithFedCM()
|
||
.then((idToken) => fetch('/api/auth/lendry', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ idToken })
|
||
}))
|
||
.catch(console.error);`
|
||
},
|
||
{
|
||
id: 'sdk-manual',
|
||
label: 'SDK — ручной вызов',
|
||
language: 'javascript',
|
||
code: `<script src="${widgetUrl}" data-auto-init="false"></script>
|
||
<script>
|
||
LendryIdOneTap.init({
|
||
clientId: '${clientIdPlaceholder}',
|
||
idpUrl: '${apiBase}',
|
||
frontendUrl: '${frontendBase}',
|
||
providerName: '${projectName}',
|
||
redirectUri: '${redirectUri}',
|
||
scope: 'openid profile email'
|
||
});
|
||
|
||
window.addEventListener('lendry-sso-onetap-success', (event) => {
|
||
const { token, method, accessToken, idToken } = event.detail;
|
||
console.log(method, token ?? idToken ?? accessToken);
|
||
});
|
||
</script>`
|
||
},
|
||
{
|
||
id: 'verify-backend',
|
||
label: 'Проверка токена на backend',
|
||
language: 'javascript',
|
||
code: `// Node.js — после получения id_token от FedCM или popup
|
||
import jwt from 'jsonwebtoken';
|
||
|
||
const ISSUER = '${apiBase}';
|
||
const CLIENT_ID = '${clientIdPlaceholder}';
|
||
|
||
function verifyIdToken(idToken) {
|
||
const payload = jwt.verify(idToken, process.env.IDP_JWT_SECRET, {
|
||
issuer: ISSUER,
|
||
audience: CLIENT_ID
|
||
});
|
||
return payload; // { sub, email, name, ... }
|
||
}
|
||
|
||
// Альтернатива: userinfo по access_token
|
||
async function fetchProfile(accessToken) {
|
||
const res = await fetch('${apiBase}/oauth/userinfo', {
|
||
headers: { Authorization: \`Bearer \${accessToken}\` }
|
||
});
|
||
if (!res.ok) throw new Error('userinfo failed');
|
||
return res.json();
|
||
}`
|
||
},
|
||
{
|
||
id: 'curl-fedcm',
|
||
label: 'FedCM endpoints (curl)',
|
||
language: 'bash',
|
||
code: `# Манифест FedCM (заголовок Sec-Fetch-Dest обязателен для Chrome)
|
||
curl -s ${webIdentityUrl} \\
|
||
-H "Sec-Fetch-Dest: webidentity" | jq
|
||
|
||
# Конфигурация провайдера (branding.name = PROJECT_NAME из админки)
|
||
curl -s ${fedcmConfigUrl} \\
|
||
-H "Sec-Fetch-Dest: webidentity" | jq
|
||
|
||
# Discovery для виджета и диагностики
|
||
curl -s ${fedcmDiscoverUrl} | jq
|
||
|
||
# Список аккаунтов (cookie lendry_fedcm_sess после входа на IdP)
|
||
curl -s ${apiBase}/fedcm/accounts \\
|
||
-H "Sec-Fetch-Dest: webidentity" \\
|
||
-H "Cookie: lendry_fedcm_sess=..." \\
|
||
--include
|
||
|
||
# Синхронизация FedCM cookie для уже залогиненного пользователя IdP
|
||
curl -s -X POST ${apiBase}/fedcm/session/sync \\
|
||
-H "Authorization: Bearer ACCESS_TOKEN"`
|
||
}
|
||
];
|
||
}
|