import { useState, FormEvent } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { Shield } from 'lucide-react'; import api from '../lib/api'; import { useAuthStore } from '../lib/store'; import { deriveKeyFromPassword, decryptMasterKey } from '../crypto'; import { socket } from '../lib/ws'; export default function LoginPage() { const navigate = useNavigate(); const { setAuth, setMasterKey, setUsername } = useAuthStore(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [totpCode, setTotpCode] = useState(''); const [require2fa, setRequire2fa] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); async function submit(e: FormEvent) { e.preventDefault(); setError(''); setLoading(true); try { const res = await api.post('/auth/login', { email, password, ...(totpCode ? { totpCode } : {}), }); if (res.data.require2fa) { setRequire2fa(true); setLoading(false); return; } const { accessToken, refreshToken, userId, kdfSalt, encryptedMasterKey, username } = res.data; // Derive key from password — never leaves the browser const derived = await deriveKeyFromPassword(password, kdfSalt); const masterKey = await decryptMasterKey(encryptedMasterKey, derived); setAuth({ accessToken, refreshToken, userId }); setMasterKey(masterKey); setUsername(username); socket.connect(accessToken); navigate('/messages'); } catch (err: any) { setError(err.response?.data?.error || 'Login failed'); } finally { setLoading(false); } } return (

CloudLink

End-to-end encrypted

Sign in

{error &&

{error}

} {!require2fa ? ( <> ) : (
setTotpCode(e.target.value)} autoFocus />
)}

No account?{' '} Register

); } function Field({ label, type, value, onChange, }: { label: string; type: string; value: string; onChange: (v: string) => void; }) { return (
onChange(e.target.value)} required />
); }