/** * TOTP Client-Side Module * Handles Two-Factor Authentication setup, verification, and management */ const TOTP = { /** * Initialize TOTP setup process */ async setupTOTP() { try { const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken'); if (!token) { showNotification('Please log in first', 'error'); return; } const response = await fetch('/api/auth/totp/setup', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); const data = await response.json(); if (!data.success) { showNotification(data.error || 'Failed to setup 2FA', 'error'); return; } // Show setup modal with QR code this.showSetupModal(data.qrCodeDataURL, data.secret); } catch (error) { console.error('TOTP setup error:', error); showNotification('Failed to setup 2FA', 'error'); } }, /** * Display TOTP setup modal with QR code */ showSetupModal(qrCodeDataURL, secret) { const modal = document.createElement('div'); modal.id = 'totpSetupModal'; modal.className = 'modal-overlay'; modal.innerHTML = `
`; document.body.appendChild(modal); // Enable finish button when checkbox is checked document.getElementById('confirmSaved')?.addEventListener('change', (e) => { document.getElementById('finishBtn').disabled = !e.target.checked; }); // Auto-focus on verify code input when shown document.getElementById('verifyCode')?.addEventListener('input', (e) => { e.target.value = e.target.value.replace(/[^0-9]/g, ''); }); }, showScanStep() { document.getElementById('step1').style.display = 'block'; document.getElementById('step2').style.display = 'none'; document.getElementById('step1').classList.add('active'); document.getElementById('step2').classList.remove('active'); }, showVerifyStep() { document.getElementById('step1').style.display = 'none'; document.getElementById('step2').style.display = 'block'; document.getElementById('step1').classList.remove('active'); document.getElementById('step2').classList.add('active'); setTimeout(() => document.getElementById('verifyCode')?.focus(), 100); }, /** * Verify TOTP code and complete setup */ async verifySetup() { const code = document.getElementById('verifyCode')?.value; if (!code || code.length !== 6) { showNotification('Please enter a 6-digit code', 'error'); return; } try { const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken'); const response = await fetch('/api/auth/totp/verify', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) }); const data = await response.json(); if (!data.success) { showNotification(data.error || 'Invalid code', 'error'); return; } // Show backup codes this.showBackupCodes(data.backupCodes); document.getElementById('step2').style.display = 'none'; document.getElementById('step3').style.display = 'block'; document.getElementById('step2').classList.remove('active'); document.getElementById('step3').classList.add('active'); showNotification('Two-factor authentication enabled!', 'success'); } catch (error) { console.error('TOTP verification error:', error); showNotification('Failed to verify code', 'error'); } }, /** * Display backup codes */ showBackupCodes(codes) { const container = document.getElementById('backupCodes'); if (!container) {return;} this.backupCodes = codes; // Store for later use container.innerHTML = `${code}
Generated: ${new Date().toLocaleString()}
Keep these codes in a safe place!
`); printWindow.document.close(); }, /** * Copy all backup codes to clipboard */ copyBackupCodes() { if (!this.backupCodes) {return;} const text = this.backupCodes.map((code, i) => `${i + 1}. ${code}`).join('\n'); navigator.clipboard.writeText(text).then(() => { showNotification('Backup codes copied to clipboard', 'success'); }).catch(() => { showNotification('Failed to copy backup codes', 'error'); }); }, /** * Complete setup and close modal */ finishSetup() { this.closeSetupModal(); showNotification('Two-factor authentication is now active!', 'success'); // Refresh 2FA status in UI if (typeof loadTOTPStatus === 'function') { loadTOTPStatus(); } }, /** * Close setup modal */ closeSetupModal() { const modal = document.getElementById('totpSetupModal'); if (modal) { modal.remove(); } }, /** * Disable TOTP for current user */ async disableTOTP() { const confirmed = confirm('Are you sure you want to disable two-factor authentication?\n\nThis will make your account less secure.'); if (!confirmed) {return;} const password = prompt('Enter your password to confirm:'); if (!password) {return;} const code = prompt('Enter your current 2FA code:'); if (!code || code.length !== 6) { showNotification('Invalid code', 'error'); return; } try { const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken'); const response = await fetch('/api/auth/totp/disable', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ password, code }) }); const data = await response.json(); if (!data.success) { showNotification(data.error || 'Failed to disable 2FA', 'error'); return; } showNotification('Two-factor authentication disabled', 'success'); // Refresh 2FA status in UI if (typeof loadTOTPStatus === 'function') { loadTOTPStatus(); } } catch (error) { console.error('Disable TOTP error:', error); showNotification('Failed to disable 2FA', 'error'); } }, /** * Regenerate backup codes */ async regenerateBackupCodes() { const confirmed = confirm('This will invalidate your old backup codes.\n\nAre you sure you want to generate new backup codes?'); if (!confirmed) {return;} const code = prompt('Enter your current 2FA code to confirm:'); if (!code || code.length !== 6) { showNotification('Invalid code', 'error'); return; } try { const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken'); const response = await fetch('/api/auth/totp/regenerate-backup-codes', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) }); const data = await response.json(); if (!data.success) { showNotification(data.error || 'Failed to regenerate codes', 'error'); return; } // Show new backup codes this.showBackupCodesModal(data.backupCodes); } catch (error) { console.error('Regenerate codes error:', error); showNotification('Failed to regenerate backup codes', 'error'); } }, /** * Show backup codes in a modal (for regeneration) */ showBackupCodesModal(codes) { this.backupCodes = codes; const modal = document.createElement('div'); modal.id = 'backupCodesModal'; modal.className = 'modal-overlay'; modal.innerHTML = ` `; document.body.appendChild(modal); showNotification('New backup codes generated', 'success'); }, closeBackupCodesModal() { const modal = document.getElementById('backupCodesModal'); if (modal) { modal.remove(); } } }; // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = TOTP; }