import { createSignal, onMount, Show, For } from 'solid-js'; import { Button } from './ui/Button'; import { getApiOrigin } from '@/lib/api-url'; interface TOTPSetupResponse { secret: string; qr_code: string; backup_codes: string[]; } interface TOTPStatus { enabled: boolean; setup: boolean; } export function TwoFactorAuth() { const [totpStatus, setTotpStatus] = createSignal(null); const [setupData, setSetupData] = createSignal(null); const [loading, setLoading] = createSignal(false); const [error, setError] = createSignal(null); const [success, setSuccess] = createSignal(null); // Form states const [setupPassword, setSetupPassword] = createSignal(''); const [verifyCode, setVerifyCode] = createSignal(''); const [enableCode, setEnableCode] = createSignal(''); const [disableCode, setDisableCode] = createSignal(''); const [disablePassword, setDisablePassword] = createSignal(''); const [backupCodeVerify, setBackupCodeVerify] = createSignal(''); const [regenerateCode, setRegenerateCode] = createSignal(''); // UI states const [showSetup, setShowSetup] = createSignal(false); const [backupCodes, setBackupCodes] = createSignal([]); const getAuthHeaders = () => { const token = localStorage.getItem('token'); return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }; }; const fetchTOTPStatus = async () => { try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/status`, { headers: getAuthHeaders(), }); if (response.ok) { const data = await response.json(); setTotpStatus(data); } } catch (err) { setError('Failed to fetch 2FA status'); } }; const setupTOTP = async () => { if (!setupPassword()) { setError('Password is required'); return; } setLoading(true); setError(null); setSuccess(null); try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/setup`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ password: setupPassword(), }), }); if (response.ok) { const data = await response.json(); setSetupData(data); setBackupCodes(data.backup_codes); setShowSetup(true); setSuccess('TOTP setup initiated. Please scan the QR code and save your backup codes.'); setSetupPassword(''); } else { const errorData = await response.json(); setError(errorData.error || 'Failed to setup TOTP'); } } catch (err) { setError('Network error. Please try again.'); } finally { setLoading(false); } }; const verifyTOTPCode = async () => { if (!verifyCode()) { setError('Verification code is required'); return; } setLoading(true); setError(null); try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/verify`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ code: verifyCode(), }), }); if (response.ok) { setSuccess('TOTP code verified successfully!'); setVerifyCode(''); } else { const errorData = await response.json(); setError(errorData.error || 'Invalid verification code'); } } catch (err) { setError('Network error. Please try again.'); } finally { setLoading(false); } }; const enableTOTP = async () => { if (!enableCode()) { setError('Enable code is required'); return; } setLoading(true); setError(null); setSuccess(null); try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/enable`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ code: enableCode(), }), }); if (response.ok) { setSuccess('Two-Factor Authentication enabled successfully!'); setEnableCode(''); setShowSetup(false); setSetupData(null); await fetchTOTPStatus(); } else { const errorData = await response.json(); setError(errorData.error || 'Failed to enable TOTP'); } } catch (err) { setError('Network error. Please try again.'); } finally { setLoading(false); } }; const disableTOTP = async () => { if (!disableCode() || !disablePassword()) { setError('Both code and password are required'); return; } setLoading(true); setError(null); setSuccess(null); try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/disable`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ code: disableCode(), password: disablePassword(), }), }); if (response.ok) { setSuccess('Two-Factor Authentication disabled successfully!'); setDisableCode(''); setDisablePassword(''); await fetchTOTPStatus(); } else { const errorData = await response.json(); setError(errorData.error || 'Failed to disable TOTP'); } } catch (err) { setError('Network error. Please try again.'); } finally { setLoading(false); } }; const verifyBackupCode = async () => { if (!backupCodeVerify()) { setError('Backup code is required'); return; } setLoading(true); setError(null); try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/verify`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ code: backupCodeVerify(), }), }); if (response.ok) { const data = await response.json(); setSuccess(`Backup code verified! ${data.remaining_codes} codes remaining.`); setBackupCodeVerify(''); } else { const errorData = await response.json(); setError(errorData.error || 'Invalid backup code'); } } catch (err) { setError('Network error. Please try again.'); } finally { setLoading(false); } }; const regenerateBackupCodes = async () => { if (!regenerateCode()) { setError('Current TOTP code is required'); return; } setLoading(true); setError(null); setSuccess(null); try { const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/regenerate`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ code: regenerateCode(), }), }); if (response.ok) { const data = await response.json(); setBackupCodes(data.backup_codes); setSuccess('Backup codes regenerated successfully!'); setRegenerateCode(''); } else { const errorData = await response.json(); setError(errorData.error || 'Failed to regenerate backup codes'); } } catch (err) { setError('Network error. Please try again.'); } finally { setLoading(false); } }; onMount(() => { fetchTOTPStatus(); }); return (

Two-Factor Authentication

{totpStatus()?.enabled ? 'Enabled' : 'Disabled'}
{/* Error and Success Messages */}
{error()}
{success()}
{/* Current Status */}

Current Status

2FA Status: {totpStatus()?.enabled ? 'Enabled' : 'Disabled'}
Setup Status: {totpStatus()?.setup ? 'Configured' : 'Not Configured'}
{/* Setup TOTP */}

Setup Two-Factor Authentication

Enable 2FA to add an extra layer of security to your account. You'll need a TOTP app like Google Authenticator or Authy.

setSetupPassword(e.currentTarget.value)} class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" placeholder="Enter your password" />
{/* TOTP Setup Process */}

Complete 2FA Setup

{/* QR Code */}

Scan QR Code

TOTP QR Code

Or manually enter this secret in your TOTP app:

{setupData()!.secret}
{/* Backup Codes */}

Backup Codes

Save these backup codes in a secure location. You can use them to access your account if you lose your TOTP device.

{(code) => ( {code} )}
{/* Verification */}

Verify Setup

setVerifyCode(e.currentTarget.value.replace(/\D/g, '').slice(0, 6))} class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" placeholder="000000" maxlength={6} />
{/* Disable 2FA */}

Disable Two-Factor Authentication

Disabling 2FA will make your account less secure. You'll need to provide your current TOTP code and password.

setDisableCode(e.currentTarget.value.replace(/\D/g, '').slice(0, 6))} class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" placeholder="000000" maxlength={6} />
setDisablePassword(e.currentTarget.value)} class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" placeholder="Enter your password" />
{/* Backup Code Management */}

Backup Code Management

{/* Verify Backup Code */}

Verify Backup Code

setBackupCodeVerify(e.currentTarget.value)} class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" placeholder="Enter backup code" />
{/* Regenerate Backup Codes */}

Regenerate Backup Codes

This will invalidate all existing backup codes and generate new ones.

setRegenerateCode(e.currentTarget.value.replace(/\D/g, '').slice(0, 6))} class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring" placeholder="Current TOTP code" maxlength={6} />
{/* Show New Backup Codes */} 0}>

New Backup Codes

Save these new backup codes in a secure location:

{(code) => ( {code} )}
); }