import { StepHeader } from '@/components/onboarding/step-header'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useEncryptionKey } from '@/contexts/encryption-key-context'; import { bufferToBase64, encrypt, exportKey, generateSalt, getAESKeyFromPBKDF, getKeyFromPassword, } from '@/lib/crypto'; import { storeKey } from '@/lib/key-storage'; import { __ } from '@/utils/i18n'; import axios from 'axios'; import { AlertCircle, CheckCircle2, KeyRound } from 'lucide-react'; import { useState } from 'react'; import { StepButton } from './step-button'; interface StepEncryptionSetupProps { onComplete: () => void; } export function StepEncryptionSetup({ onComplete }: StepEncryptionSetupProps) { const { refreshKeyState } = useEncryptionKey(); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [storagePreference, setStoragePreference] = useState< 'session' | 'persistent' >('session'); const [processing, setProcessing] = useState(false); const [error, setError] = useState(null); const passwordStrength = getPasswordStrength(password); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(null); if (password.length < 12) { setError(__('Password must be at least 12 characters')); return; } if (password !== confirmPassword) { setError(__('Passwords do not match')); return; } setProcessing(true); try { const salt = generateSalt(); const pbkdfKey = await getKeyFromPassword(password); const aesKey = await getAESKeyFromPBKDF(pbkdfKey, salt); const { encrypted, iv } = await encrypt('Hello, world', aesKey); const exportedKey = await exportKey(aesKey); await axios.post('/api/encryption/setup', { salt: bufferToBase64(salt), encrypted_content: encrypted, iv: iv, }); storeKey(exportedKey, storagePreference === 'persistent'); refreshKeyState(); onComplete(); } catch (err) { console.error('Encryption setup error:', err); setError(__('Failed to setup encryption. Please try again.')); setProcessing(false); } } return (
setPassword(e.target.value)} placeholder={__('Enter a strong password')} disabled={processing} autoComplete="new-password" required minLength={12} />
{[1, 2, 3, 4].map((level) => (
))}
{__(passwordStrength.label)}
= 12 ? 'text-emerald-600' : 'text-muted-foreground'}`} > {password.length} {__('/12 min')}
setConfirmPassword(e.target.value)} placeholder={__('Confirm your password')} disabled={processing} autoComplete="new-password" required /> {confirmPassword && password === confirmPassword && (
{__('Passwords match')}
)}

{storagePreference === 'session' ? __( 'Your key will be cleared when you close the browser.', ) : __('Your key will be stored until you log out.')}

{error && (
{error}
)}
); } function getPasswordStrength(password: string): { level: number; label: string; color: string; } { if (!password) { return { level: 0, label: '', color: 'bg-muted' }; } let score = 0; if (password.length >= 6) score++; if (password.length >= 12) score++; if (/[A-Z]/.test(password) || /[a-z]/.test(password)) score++; if (/\d/.test(password)) score++; if (/[0-9]/.test(password)) score++; if (score <= 1) { return { level: 1, label: 'Weak', color: 'bg-red-500' }; } if (score === 2) { return { level: 2, label: 'Fair', color: 'bg-orange-500' }; } if (score === 3) { return { level: 3, label: 'Good', color: 'bg-yellow-500' }; } return { level: 4, label: 'Strong', color: 'bg-emerald-500' }; }