/
Al
/
TESamples
Обзор
Документация
Войти
/
Al
/
TESamples
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
apps/spa/src/app.js
115 строк
4 KB
Al
Initial Commit
11 май 2026, 17:40
11 май 2026, 17:40
0a04577
Код
Авторство
О чём код?
// Браузерный фронт демо TokenExchange — vanilla JS без сборки. // // Реализует §2 Рекомендаций для клиента (см. docs/client-recommendations.md#2 // «Хранение токенов в браузерной части»): // §2.1 — токенов в браузере нет, всё через cookie-сессию с BFF (#21); // §2.2 — обмен токенами делает BFF, не браузер (#22); // §2.3 — экран согласия пользователя перед делегированием (#23). // // Эндпоинты, к которым обращается этот скрипт (через nginx-проксирование // /auth/* и /api/* → BFF http://bff-agent:8080): // GET /auth/me — текущая сессия // GET /auth/login — начать Authorization Code + PKCE // POST /auth/logout — закрыть сессию // GET /agent/task/preview — данные для экрана согласия // POST /agent/task/from-session — выполнить обмен и пройти цепочку // POST /agent/task/revoke — отозвать у агента полномочия const $ = (sel) => document.querySelector(sel); const sections = { login: $('#login-section'), task: $('#task-section'), revoked: $('#revoked-section'), }; function show(name) { Object.entries(sections).forEach(([k, el]) => { el.hidden = k !== name; }); } async function fetchJson(url, opts = {}) { const r = await fetch(url, { credentials: 'include', ...opts }); if (!r.ok) throw Object.assign(new Error('HTTP ' + r.status), { status: r.status, body: await r.text() }); return r.json(); } async function refreshUser() { try { const me = await fetchJson('/auth/me'); $('#user-bar').textContent = `вошли как ${me.preferred_username} (idp: ${me.idp})`; show('task'); return true; } catch (e) { $('#user-bar').textContent = 'не вошли'; show('login'); return false; } } function renderConsentTable(p) { const t = $('#consent-table'); t.innerHTML = ` <tr><th>Пользователь</th><td>${p.user.preferred_username} <code>${p.user.sub.slice(0,8)}…</code></td></tr> <tr><th>Агент</th><td>${p.agent.name} v${p.agent.version} (type=${p.agent.type}, client_id=${p.agent.client_id})</td></tr> <tr><th>Делегируемый scope</th><td><strong>${p.scope_to_grant}</strong> (down-scoped, FR-01b)</td></tr> <tr><th>Audience цепочка</th><td>${p.audience_chain}</td></tr> <tr><th>TTL делегирования</th><td>${p.delegation_ttl_seconds} с (формула FR-15.5)</td></tr> `; } function renderResult(r) { $('#result-claims').textContent = JSON.stringify(r.delegated_claims, null, 2); $('#result-chain').textContent = JSON.stringify(r.downstream, null, 2); $('#result-card').hidden = false; } // ---- Event handlers ---- $('#btn-login').addEventListener('click', () => { window.location.href = '/auth/login'; }); $('#btn-preview').addEventListener('click', async () => { try { const p = await fetchJson('/api/agent/task/preview'); renderConsentTable(p); $('#consent-card').hidden = false; } catch (e) { if (e.status === 401) await refreshUser(); else alert('Ошибка: ' + e.message); } }); $('#btn-cancel').addEventListener('click', () => { $('#consent-card').hidden = true; }); $('#btn-confirm').addEventListener('click', async () => { $('#btn-confirm').disabled = true; try { const r = await fetchJson('/api/agent/task/from-session', { method: 'POST' }); $('#consent-card').hidden = true; renderResult(r); } catch (e) { alert('Ошибка: ' + (e.body || e.message)); } finally { $('#btn-confirm').disabled = false; } }); $('#btn-revoke').addEventListener('click', async () => { if (!confirm('Отозвать у агента полномочия? Все backend-сервисы перестанут принимать ваши токены.')) return; try { await fetchJson('/api/agent/task/revoke', { method: 'POST' }); show('revoked'); } catch (e) { alert('Ошибка: ' + e.message); } }); $('#btn-relogin').addEventListener('click', () => { window.location.href = '/auth/login'; }); // ---- Init ---- refreshUser();