import React, { useState, useEffect } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { useConvex } from 'convex/react'; import { api } from '../../../../convex/_generated/api'; function parseInviteParams(input) { const codeMatch = input.match(/[?&]code=([^&]+)/); const keyMatch = input.match(/[?&]key=([^&]+)/); if (codeMatch && keyMatch) return { code: codeMatch[1], secret: keyMatch[1] }; return null; } const Register = () => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [inviteKeys, setInviteKeys] = useState(null); const [inviteLinkInput, setInviteLinkInput] = useState(''); const [activeInviteCode, setActiveInviteCode] = useState(null); const navigate = useNavigate(); const location = useLocation(); const convex = useConvex(); const processInvite = async (code, secret) => { if (!window.cryptoAPI) { setError("Critical Error: Secure Crypto API missing. Run in Electron."); return; } try { const result = await convex.query(api.invites.use, { code }); if (result.error) throw new Error(result.error); const blob = JSON.parse(result.encryptedPayload); const decrypted = await window.cryptoAPI.decryptData(blob.c, secret, blob.iv, blob.t); const keys = JSON.parse(decrypted); console.log('Invite keys decrypted successfully:', Object.keys(keys).length); setInviteKeys(keys); setActiveInviteCode(code); setError(''); } catch (err) { console.error('Invite error:', err); setError('Invite verification failed: ' + err.message); } }; useEffect(() => { const params = new URLSearchParams(location.search); const code = params.get('code'); const secret = params.get('key'); if (code && secret) { console.log('Invite detected in URL'); processInvite(code, secret); } }, [location]); const handleManualInvite = () => { const parsed = parseInviteParams(inviteLinkInput); if (parsed) { processInvite(parsed.code, parsed.secret); } else { setError("Invalid invite link format."); } }; const handleRegister = async (e) => { e.preventDefault(); setError(''); if (password !== confirmPassword) { setError('Passwords do not match'); return; } setLoading(true); try { console.log('Starting registration for:', username); const salt = await window.cryptoAPI.randomBytes(16); const mk = await window.cryptoAPI.randomBytes(32); const { dek, dak } = await window.cryptoAPI.deriveAuthKeys(password, salt); const encryptedMK = JSON.stringify(await window.cryptoAPI.encryptData(mk, dek)); const hak = await window.cryptoAPI.sha256(dak); const keys = await window.cryptoAPI.generateKeys(); const encryptedPrivateKeys = JSON.stringify({ rsa: await window.cryptoAPI.encryptData(keys.rsaPriv, mk), ed: await window.cryptoAPI.encryptData(keys.edPriv, mk) }); const data = await convex.mutation(api.auth.createUserWithProfile, { username, salt, encryptedMK, hak, publicKey: keys.rsaPub, signingKey: keys.edPub, encryptedPrivateKeys, inviteCode: activeInviteCode || undefined }); if (data.error) throw new Error(data.error); console.log('Registration successful:', data); if (inviteKeys && data.userId) { console.log('Uploading invite keys...'); const batchKeys = await Promise.all( Object.entries(inviteKeys).map(async ([channelId, channelKeyHex]) => { const payload = JSON.stringify({ [channelId]: channelKeyHex }); const encryptedKeyBundle = await window.cryptoAPI.publicEncrypt(keys.rsaPub, payload); return { channelId, userId: data.userId, encryptedKeyBundle, keyVersion: 1 }; }).map(p => p.catch(err => { console.error('Failed to encrypt key for channel:', err); return null; })) ); const validKeys = batchKeys.filter(Boolean); if (validKeys.length > 0) { await convex.mutation(api.channelKeys.uploadKeys, { keys: validKeys }); console.log('Uploaded invite keys'); } } navigate('/'); } catch (err) { console.error('Registration error:', err); setError(err.message); } finally { setLoading(false); } }; return (
Join the secure chat! {inviteKeys ? '(Invite Active)' : ''}
Registration is Invite-Only.
Please paste a valid invite link above to proceed.
{/* Backdoor for First User */}setInviteKeys({})}> (First User / Verify Setup)