(function (root, factory) { if (typeof module !== 'undefined' && module.exports) module.exports = factory; else root.attachSecurityCenter = boundary => factory({ root, boundary }); })(typeof window !== 'undefined' ? window : this, function attachSecurityCenter({ root, boundary }) { 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'); let activityCursor = null; 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) await boundary.signOut(); else if (await boundary.revokeActiveDevice(device)) await renderDevices(); }); 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(); } 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; row.append(title, details); 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', 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); 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; devicesSheet.hidden = false; root.document.getElementById('close-active-devices')?.focus(); 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, renderDevices, renderPasskeys, renderSecurityActivity }; });