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: ` ` }, { 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: ` ` }, { 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"` } ]; }