(function (root, factory) { if (typeof module !== 'undefined' && module.exports) module.exports = factory; else root.attachSecurityCenter = (boundary, options = {}) => factory({ root, boundary, ...options }); })(typeof window !== 'undefined' ? window : this, function attachSecurityCenter({ root, boundary, onOpen = () => {}, onClose = () => {} }) { const devicesButton = root.document.getElementById('active-devices'); const devicesSheet = root.document.getElementById('active-devices-sheet'); const devicesList = root.document.getElementById('active-devices-list'); const devicesStatus = root.document.getElementById('active-devices-status'); const passkeysList = root.document.getElementById('enrolled-passkeys-list'); const passkeysStatus = root.document.getElementById('enrolled-passkeys-status'); const enrollPasskey = root.document.getElementById('enroll-passkey'); const activityList = root.document.getElementById('security-activity-list'); const activityStatus = root.document.getElementById('security-activity-status'); const loadMoreActivity = root.document.getElementById('load-more-security-activity'); const sectionButtons = Object.fromEntries(['activity', 'devices', 'passkeys'].map(section => [ section, root.document.getElementById(`security-section-${section}`), ])); const sections = Object.fromEntries(['activity', 'devices', 'passkeys'].map(section => [ section, root.document.getElementById(`security-${section}-section`), ])); let activityCursor = null; let backgroundInert = null; const backgroundElements = ['header', 'main', '#mobile-task-dock'] .map(selector => root.document.querySelector?.(selector)) .filter(Boolean); const focusableControls = () => [...(devicesSheet?.querySelectorAll?.( 'button:not([disabled]), select:not([disabled]), input:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])' ) || [])].filter(control => !control.hidden && !control.disabled); const containBackground = () => { if (!backgroundInert) { backgroundInert = new Map(backgroundElements.map(element => [element, element.inert])); } backgroundInert.forEach((_wasInert, element) => { element.inert = true; }); }; const releaseBackground = () => { if (!backgroundInert) return; backgroundInert.forEach((wasInert, element) => { element.inert = wasInert; }); backgroundInert = null; }; const finishClose = () => { devicesSheet.hidden = true; releaseBackground(); devicesButton?.focus(); onClose(); }; const navigate = (section, { history = true } = {}) => { if (!sections[section]) section = 'activity'; Object.entries(sectionButtons).forEach(([name, button]) => { if (!button) return; if (name === section) button.setAttribute('aria-current', 'page'); else button.removeAttribute('aria-current'); }); sections[section]?.scrollIntoView?.({ block: 'start' }); if (history && root.history?.pushState) { root.history.pushState( { ...(root.history.state || {}), stackchainSecuritySection: section }, '', `${root.location.pathname}${root.location.search || ''}#security/${section}`, ); } }; Object.entries(sectionButtons).forEach(([section, button]) => { button?.addEventListener('click', () => navigate(section)); }); root.document.getElementById('close-active-devices')?.addEventListener('click', () => { if (root.history?.state?.stackchainSecuritySection) root.history.back(); }); const handleRouteChange = section => { if (section && !devicesSheet.hidden) { containBackground(); navigate(section, { history: false }); return; } if (!devicesSheet.hidden) { finishClose(); } }; root.addEventListener?.('popstate', event => { handleRouteChange(event.state?.stackchainSecuritySection); }); root.addEventListener?.('hashchange', () => { handleRouteChange(root.history?.state?.stackchainSecuritySection); }); root.document.addEventListener?.('keydown', event => { if (devicesSheet.hidden) return; if (event.key === 'Escape') { event.preventDefault(); if (root.history?.state?.stackchainSecuritySection) root.history.back(); else finishClose(); return; } if (event.key !== 'Tab') return; const controls = focusableControls(); if (!controls.length) return; const first = controls[0]; const last = controls[controls.length - 1]; if (event.shiftKey && event.target === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && event.target === last) { event.preventDefault(); first.focus(); } }); const renderDevices = async () => { devicesStatus.textContent = 'Loading active devices…'; devicesList.replaceChildren(); try { const devices = await boundary.listActiveDevices(); devices.forEach(device => { const row = root.document.createElement('article'); row.className = 'active-device'; const details = root.document.createElement('div'); const label = root.document.createElement('strong'); label.textContent = device.device_label; const timing = root.document.createElement('span'); timing.className = 'small muted'; timing.textContent = `Signed in ${new Date(device.created_at * 1000).toLocaleString()} · expires ${new Date(device.expires_at * 1000).toLocaleString()}`; details.append(label, timing); if (device.current) { const current = root.document.createElement('span'); current.className = 'active-device-current'; current.textContent = 'This device'; details.append(current); } const revoke = root.document.createElement('button'); revoke.type = 'button'; revoke.textContent = device.current ? 'Sign out' : 'Revoke'; revoke.addEventListener('click', async () => { if (device.current && boundary.requestSignOut) await boundary.requestSignOut(revoke); else if (device.current) await boundary.signOut(); else if (await boundary.revokeActiveDevice(device)) { await Promise.all([renderDevices(), renderSecurityActivity()]); } }); row.append(details, revoke); devicesList.append(row); }); devicesStatus.textContent = devices.length ? `${devices.length} active device${devices.length === 1 ? '' : 's'}` : 'No active devices.'; } catch (_error) { devicesStatus.textContent = 'Active devices could not be loaded. Try again.'; } }; const renderPasskeys = async () => { if (!passkeysList || !passkeysStatus) return; passkeysStatus.textContent = 'Loading enrolled passkeys…'; passkeysList.replaceChildren(); try { const enrolled = await boundary.listPasskeys(); enrolled.forEach(passkey => { const row = root.document.createElement('article'); row.className = 'enrolled-passkey'; const details = root.document.createElement('div'); const label = root.document.createElement('strong'); label.textContent = passkey.device_label; const timing = root.document.createElement('span'); timing.className = 'small muted'; const state = passkey.current ? 'This active device' : (passkey.active ? 'Active device' : 'No active session'); timing.textContent = `Enrolled ${new Date(passkey.created_at * 1000).toLocaleString()} · ${state}`; details.append(label, timing); const remove = root.document.createElement('button'); remove.type = 'button'; remove.textContent = 'Remove passkey'; remove.addEventListener('click', async () => { remove.disabled = true; passkeysStatus.textContent = `Removing passkey for ${passkey.device_label}…`; try { const outcome = await boundary.revokePasskey(passkey); if (outcome) { passkeysStatus.textContent = outcome.current_session ? 'Passkey removed. This session remains active; keep your recovery token available.' : 'Passkey removed.'; await renderPasskeys(); if (outcome.session_revoked) await renderDevices(); await renderSecurityActivity(); } else { remove.disabled = false; passkeysStatus.textContent = `${enrolled.length} enrolled passkey${enrolled.length === 1 ? '' : 's'}`; } } catch (_error) { passkeysStatus.textContent = 'Passkey could not be removed. Refresh to verify before retrying.'; remove.disabled = false; } }); row.append(details, remove); passkeysList.append(row); }); passkeysStatus.textContent = enrolled.length ? `${enrolled.length} enrolled passkey${enrolled.length === 1 ? '' : 's'}` : 'No passkeys enrolled.'; } catch (_error) { passkeysStatus.textContent = 'Enrolled passkeys could not be loaded. Try again.'; } }; const renderSecurityActivity = async (append = false) => { if (!activityList || !activityStatus || !loadMoreActivity) return; activityStatus.textContent = append ? 'Loading older activity…' : 'Loading security activity…'; loadMoreActivity.hidden = true; if (!append) { activityCursor = null; activityList.replaceChildren(); } try { const page = await boundary.listSecurityEvents(append ? activityCursor : null); if (!append) { page.authentication_alerts.forEach(alert => { const row = root.document.createElement('article'); row.className = 'security-event authentication-alert'; const formatted = boundary.formatAuthenticationAlert(alert); const title = root.document.createElement('strong'); title.textContent = formatted.title; const details = root.document.createElement('span'); details.className = 'small muted'; details.textContent = formatted.detail; const action = root.document.createElement('button'); action.type = 'button'; action.textContent = 'Review devices'; action.addEventListener('click', () => navigate('devices')); row.append(title, details, action); activityList.append(row); }); } const labels = { sign_in: 'Signed in', sign_out: 'Signed out', passkey_enrolled: 'Passkey enrolled', passkey_counter_anomaly: 'Passkey counter anomaly', device_revoked: 'Device access revoked', all_sessions_revoked: 'All device access revoked', issue_closed: 'Issue closed', pull_merged: 'Pull request merged', source_branch_deleted: 'Source branch deleted', comment_deleted: 'Comment deleted', pull_review_approved: 'Pull request approved', pull_review_changes_requested: 'Changes requested', gitea_time_logged: 'Gitea time logged', }; page.events.forEach(event => { const row = root.document.createElement('article'); row.className = 'security-event'; const title = root.document.createElement('strong'); title.textContent = labels[event.kind] || 'Security event'; const details = root.document.createElement('span'); details.className = 'small muted'; const context = [event.device_label, event.method, event.target, event.kind === 'passkey_counter_anomaly' ? 'Remove and re-enroll this passkey if the alert repeats' : null, event.status === 'pending' ? 'Outcome confirmation pending' : null] .filter(value => typeof value === 'string' && value).join(' · '); details.textContent = `${new Date(event.created_at * 1000).toLocaleString()}${context ? ' · ' + context : ''}`; row.append(title, details); if (event.kind === 'passkey_counter_anomaly') { const action = root.document.createElement('button'); action.type = 'button'; action.textContent = 'Review passkeys'; action.addEventListener('click', () => navigate('passkeys')); row.append(action); } activityList.append(row); }); activityCursor = page.next_cursor; activityStatus.textContent = activityList.children.length ? `${activityList.children.length} recent security event${activityList.children.length === 1 ? '' : 's'}` : 'No security activity yet.'; loadMoreActivity.textContent = 'Load older activity'; loadMoreActivity.hidden = !activityCursor; } catch (_error) { activityStatus.textContent = 'Security activity could not be loaded.'; loadMoreActivity.textContent = 'Retry activity'; loadMoreActivity.hidden = false; } }; const open = () => { if (!devicesSheet) return; onOpen(); containBackground(); devicesSheet.hidden = false; root.document.getElementById('close-active-devices')?.focus(); navigate('activity', { history: root.history?.state?.stackchainSecuritySection !== 'activity' }); return Promise.all([renderDevices(), renderPasskeys(), renderSecurityActivity()]); }; devicesButton?.addEventListener('click', open); loadMoreActivity?.addEventListener('click', () => renderSecurityActivity(Boolean(activityCursor))); enrollPasskey?.addEventListener('click', async () => { enrollPasskey.disabled = true; devicesStatus.textContent = 'Waiting for your device passkey…'; try { await boundary.enrollPasskey(); devicesStatus.textContent = 'Passkey enrolled. You can use it at sign-in and authorization prompts.'; await renderPasskeys(); } catch (_error) { devicesStatus.textContent = 'Passkey enrollment was not completed. Try again.'; } finally { enrollPasskey.disabled = false; } }); return { open, navigate, renderDevices, renderPasskeys, renderSecurityActivity }; });