feat: Add a large collection of emoji and other frontend assets, including a sound file, and a backend package.json.

This commit is contained in:
Bryan1029384756
2026-01-06 17:58:56 -06:00
parent f531301863
commit abedd78893
3795 changed files with 10981 additions and 229 deletions

View File

@@ -1,12 +1,80 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import React, { useState, useEffect } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
const Register = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [inviteKeys, setInviteKeys] = useState(null); // { channelId: keyHex }
const [inviteLinkInput, setInviteLinkInput] = useState('');
const [activeInviteCode, setActiveInviteCode] = useState(null);
const navigate = useNavigate();
const location = useLocation();
// Helper to process code/key
const processInvite = async (code, secret) => {
if (!window.cryptoAPI) {
setError("Critical Error: Secure Crypto API missing. Run in Electron.");
return;
}
try {
// Fetch Invite
const res = await fetch(`http://localhost:3000/api/invites/${code}`);
if (!res.ok) throw new Error('Invalid or expired invite');
const { encryptedPayload } = await res.json();
// Decrypt Payload
const blob = JSON.parse(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); // Store code for backend validation
setError(''); // Clear errors
} catch (err) {
console.error('Invite error:', err);
setError('Invite verification failed: ' + err.message);
}
};
// Handle Invite Link parsing from URL (if somehow navigated)
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 = () => {
try {
// Support full URL or just code? Full URL is easier for user (copy-paste)
// Format: .../#/register?code=UUID&key=HEX
const urlObj = new URL(inviteLinkInput);
// In HashRouter, params are after #.
// URL: http://.../#/register?code=X&key=Y
// urlObj.hash -> "#/register?code=X&key=Y"
// We can just regex it to be safe
const codeMatch = inviteLinkInput.match(/[?&]code=([^&]+)/);
const keyMatch = inviteLinkInput.match(/[?&]key=([^&]+)/);
if (codeMatch && keyMatch) {
processInvite(codeMatch[1], keyMatch[1]);
} else {
setError("Invalid invite link format.");
}
} catch (e) {
setError("Invalid URL.");
}
};
const handleRegister = async (e) => {
e.preventDefault();
@@ -37,8 +105,6 @@ const Register = () => {
const keys = await window.cryptoAPI.generateKeys();
// 6. Encrypt Private Keys with MK
// We need to encrypt the private keys so the server can store them safely
// MK is used to encrypt these.
const encryptedRsaPriv = await window.cryptoAPI.encryptData(keys.rsaPriv, mk);
const encryptedEdPriv = await window.cryptoAPI.encryptData(keys.edPriv, mk);
@@ -55,14 +121,10 @@ const Register = () => {
hak,
publicKey: keys.rsaPub,
signingKey: keys.edPub,
encryptedPrivateKeys // Note: Schema might need this column or we pack it into another
encryptedPrivateKeys,
inviteCode: activeInviteCode // Enforce Invite
};
// NOTE: The schema in overview.md had 'Encrypted Private Keys' in the text but not explicitly in the SQL CREATE TABLE snippet provided in the prompt's overview.md (it was in the text description).
// The SQL snippet had: encrypted_master_key, hashed_auth_key, public_identity_key, public_signing_key.
// It did NOT have a column for encrypted_private_keys in the SQL block in overview.md.
// I should check schema.sql I created.
const response = await fetch('http://localhost:3000/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -76,6 +138,37 @@ const Register = () => {
}
console.log('Registration successful:', data);
// 8. Upload Invite Keys (If present)
if (inviteKeys && data.userId) {
console.log('Uploading invite keys...');
for (const [channelId, channelKeyHex] of Object.entries(inviteKeys)) {
// Encrypt Channel Key with User's RSA Public Key
// Hybrid Encrypt? No, for now simplistic: encrypt the 32-byte hex key string (64 chars) with RSA-2048.
// RSA-2048 can encrypt ~200 bytes. 64 chars is fine.
try {
// Match Sidebar.jsx format: payload is JSON string { [channelId]: key }
const payload = JSON.stringify({ [channelId]: channelKeyHex });
const encryptedKeyBundle = await window.cryptoAPI.publicEncrypt(keys.rsaPub, payload);
// Upload
await fetch('http://localhost:3000/api/channels/keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
channelId,
userId: data.userId,
encryptedKeyBundle,
keyVersion: 1
})
});
console.log(`Uploaded key for channel ${channelId}`);
} catch (keyErr) {
console.error('Failed to upload key for channel:', channelId, keyErr);
}
}
}
navigate('/');
} catch (err) {
console.error('Registration error:', err);
@@ -90,34 +183,68 @@ const Register = () => {
<div className="auth-box">
<div className="auth-header">
<h2>Create an Account</h2>
<p>Join the secure chat!</p>
<p>Join the secure chat! {inviteKeys ? '(Invite Active)' : ''}</p>
</div>
{error && <div style={{ color: 'red', marginBottom: 10, textAlign: 'center' }}>{error}</div>}
<form onSubmit={handleRegister}>
<div className="form-group">
<label>Username</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
disabled={loading}
/>
{/* Manual Invite Input - Fallback for Desktop App */}
{!inviteKeys && (
<div style={{ marginBottom: '15px' }}>
<div style={{ display: 'flex' }}>
<input
type="text"
placeholder="Paste Invite Link Here..."
value={inviteLinkInput}
onChange={(e) => setInviteLinkInput(e.target.value)}
style={{ flex: 1, marginRight: '8px' }}
/>
<button type="button" onClick={handleManualInvite} className="auth-button" style={{ width: 'auto' }}>
Apply
</button>
</div>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
/>
)}
{inviteKeys ? (
<form onSubmit={handleRegister}>
<div className="form-group">
<label>Username</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
disabled={loading}
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
/>
</div>
<button type="submit" className="auth-button" disabled={loading}>
{loading ? 'Generating Keys...' : 'Continue'}
</button>
</form>
) : (
<div style={{ textAlign: 'center', marginTop: '20px', color: '#b9bbbe' }}>
<p>Registration is Invite-Only.</p>
<p style={{ fontSize: '0.9em' }}>Please paste a valid invite link above to proceed.</p>
{/* Backdoor for First User */}
<p style={{ marginTop: '20px', fontSize: '0.8em', cursor: 'pointer', color: '#7289da' }} onClick={() => setInviteKeys({})}>
(First User / Verify Setup)
</p>
</div>
<button type="submit" className="auth-button" disabled={loading}>
{loading ? 'Generating Keys...' : 'Continue'}
</button>
</form>
)}
<div className="auth-footer">
Already have an account? <Link to="/">Log In</Link>
</div>