// ===== TOTP SETTINGS ===== (function() { injectModal('totp-settings-modal', `

Authentication Settings

TOTP is not configured
or
`); async function loadTotpSettings() { try { const res = await fetch('/api/v1/totp/config'); const data = await res.json(); if (!data.success) return; const { enabled, sessionDuration, isSetUp } = data.config; const statusDot = document.getElementById('totp-status-dot'); const statusText = document.getElementById('totp-status-text'); const statusBanner = document.getElementById('totp-status-banner'); const setupSection = document.getElementById('totp-setup-section'); const qrSection = document.getElementById('totp-qr-section'); const durationSection = document.getElementById('totp-duration-section'); const disableSection = document.getElementById('totp-disable-section'); if (enabled && isSetUp) { statusDot.style.background = 'var(--ok-fg, #7ef2ff)'; statusBanner.style.borderColor = 'var(--ok-fg, #7ef2ff)'; statusBanner.style.background = 'color-mix(in srgb, var(--ok-fg) 8%, transparent)'; statusText.textContent = 'TOTP is active'; statusText.style.color = 'var(--ok-fg, #7ef2ff)'; setupSection.style.display = 'none'; qrSection.style.display = 'none'; durationSection.style.display = 'block'; disableSection.style.display = 'block'; document.getElementById('totp-duration-select').value = sessionDuration; } else { statusDot.style.background = 'var(--muted)'; statusBanner.style.borderColor = 'var(--border)'; statusBanner.style.background = 'transparent'; statusText.textContent = 'TOTP is not configured'; statusText.style.color = 'var(--muted)'; setupSection.style.display = 'block'; qrSection.style.display = 'none'; durationSection.style.display = 'none'; disableSection.style.display = 'none'; } // Update the Auth card in the top row updateAuthCard(enabled && isSetUp, sessionDuration); } catch (e) { console.warn('Failed to load TOTP settings:', e); } } // Duration label helper const DURATION_LABELS = { '15m': '15 min', '30m': '30 min', '1h': '1 hour', '2h': '2 hours', '4h': '4 hours', '8h': '8 hours', '12h': '12 hours', '24h': '24 hours', 'never': 'Disabled' }; function updateAuthCard(active, duration) { const card = document.getElementById('auth-card'); const pill = document.getElementById('auth-pill'); const dot = document.getElementById('auth-dot'); const statusText = document.getElementById('auth-status-text'); if (!card) return; if (active) { card.setAttribute('data-status', 'on'); pill.className = 'badge on'; pill.textContent = 'YES'; dot.className = 'dot ok at-bl'; statusText.textContent = 'Session: ' + (DURATION_LABELS[duration] || duration); } else { card.setAttribute('data-status', 'off'); pill.className = 'badge off'; pill.textContent = 'NO'; dot.className = 'dot bad at-bl'; statusText.textContent = 'Not configured'; } } // Setup button (generate new secret) document.getElementById('totp-setup-btn')?.addEventListener('click', async () => { try { const res = await secureFetch('/api/v1/totp/setup', { method: 'POST' }); const data = await res.json(); if (data.success) { document.getElementById('totp-qr-image').src = data.qrCode; document.getElementById('totp-manual-key').textContent = data.manualKey; document.getElementById('totp-setup-section').style.display = 'none'; document.getElementById('totp-qr-section').style.display = 'block'; document.getElementById('totp-setup-code').value = ''; document.getElementById('totp-setup-error').textContent = ''; document.getElementById('totp-setup-code').focus(); } } catch (e) { console.error('TOTP setup failed:', e); } }); // Import existing secret button document.getElementById('totp-import-btn')?.addEventListener('click', async () => { const secret = document.getElementById('totp-import-key').value.trim(); const errorEl = document.getElementById('totp-import-error'); errorEl.textContent = ''; if (!secret) { errorEl.textContent = 'Paste a Base32 secret key first'; return; } try { const res = await secureFetch('/api/v1/totp/setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ secret }) }); const data = await res.json(); if (data.success) { errorEl.textContent = ''; document.getElementById('totp-qr-image').src = data.qrCode; document.getElementById('totp-manual-key').textContent = data.manualKey; document.getElementById('totp-setup-section').style.display = 'none'; document.getElementById('totp-qr-section').style.display = 'block'; document.getElementById('totp-setup-code').value = ''; document.getElementById('totp-setup-error').textContent = ''; document.getElementById('totp-setup-code').focus(); } else { errorEl.textContent = data.error || data.message || 'Import failed'; } } catch (e) { errorEl.textContent = 'Connection error — try refreshing the page'; } }); // Copy key button document.getElementById('totp-copy-key')?.addEventListener('click', () => { const key = document.getElementById('totp-manual-key').textContent; navigator.clipboard.writeText(key).then(() => { const btn = document.getElementById('totp-copy-key'); btn.textContent = '✅'; setTimeout(() => { btn.textContent = '📋'; }, 2000); }); }); // Confirm setup document.getElementById('totp-confirm-setup')?.addEventListener('click', async () => { const code = document.getElementById('totp-setup-code').value; const errorEl = document.getElementById('totp-setup-error'); if (!/^\d{6}$/.test(code)) { errorEl.textContent = 'Enter a 6-digit code'; return; } try { const res = await secureFetch('/api/v1/totp/verify-setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) }); const data = await res.json(); if (data.success) { errorEl.textContent = ''; loadTotpSettings(); // Updates both modal and card } else { errorEl.textContent = data.error || 'Invalid code'; } } catch (e) { errorEl.textContent = 'Connection error'; } }); // Allow Enter key on setup code input document.getElementById('totp-setup-code')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') document.getElementById('totp-confirm-setup')?.click(); }); // Duration change document.getElementById('totp-duration-select')?.addEventListener('change', async (e) => { try { await secureFetch('/api/v1/totp/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionDuration: e.target.value }) }); loadTotpSettings(); // Refresh modal + card (handles "never" disabling TOTP) } catch (err) { console.error('Failed to update session duration:', err); } }); // Disable TOTP document.getElementById('totp-disable-btn')?.addEventListener('click', async () => { if (!confirm('Disable TOTP authentication? All services will be accessible without a code.')) return; try { const res = await secureFetch('/api/v1/totp/disable', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); const data = await res.json(); if (data.success) loadTotpSettings(); } catch (e) { console.error('Failed to disable TOTP:', e); } }); // Open settings modal from Auth card document.getElementById('auth-settings-btn')?.addEventListener('click', () => { loadTotpSettings(); openModal('totp-settings-modal'); }); // Close settings modal document.getElementById('totp-modal-close')?.addEventListener('click', () => { closeModal('totp-settings-modal'); }); // Backdrop click to close document.getElementById('totp-settings-modal')?.addEventListener('click', (e) => { if (e.target.id === 'totp-settings-modal') { closeModal('totp-settings-modal'); } }); // Update auth card on page load window._updateAuthCard = updateAuthCard; (async () => { try { const res = await fetch('/api/v1/totp/config'); const data = await res.json(); if (data.success) { const active = data.config.enabled && data.config.isSetUp; updateAuthCard(active, data.config.sessionDuration); } } catch (e) { console.error('[AuthCard] Failed to update:', e); } })(); })();