release: prepare v4.8.5 security hardening release
This commit is contained in:
+112
-15
@@ -36,6 +36,31 @@
|
||||
|
||||
// Verification Component
|
||||
const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => {
|
||||
const [sasInput, setSasInput] = React.useState('');
|
||||
const [error, setError] = React.useState('');
|
||||
const normalizedExpectedLength = (verificationCode || '').replace(/[-\s]/g, '').length;
|
||||
const normalizedInputLength = sasInput.replace(/[-\s]/g, '').length;
|
||||
const canConfirm = !localConfirmed && normalizedExpectedLength > 0 && normalizedInputLength === normalizedExpectedLength;
|
||||
|
||||
React.useEffect(() => {
|
||||
setSasInput('');
|
||||
setError('');
|
||||
}, [verificationCode]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
setError('');
|
||||
await onConfirm(sasInput);
|
||||
} catch (confirmationError) {
|
||||
setSasInput('');
|
||||
if (confirmationError?.message === 'SAS_MAX_ATTEMPTS') {
|
||||
setError('Too many incorrect attempts. Session reset for safety.');
|
||||
} else {
|
||||
setError('Incorrect code. Check it with your peer and try again.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return React.createElement('div', {
|
||||
className: "card-minimal rounded-xl p-6 border-purple-500/20"
|
||||
}, [
|
||||
@@ -63,7 +88,7 @@
|
||||
React.createElement('p', {
|
||||
key: 'description',
|
||||
className: "text-secondary text-sm"
|
||||
}, "Verify the security code with your contact via another communication channel (voice, SMS, etc.):"),
|
||||
}, "Compare this code with your peer out-of-band, then type the same code below to unlock the chat."),
|
||||
React.createElement('div', {
|
||||
key: 'code-display',
|
||||
className: "text-center"
|
||||
@@ -73,6 +98,36 @@
|
||||
className: "verification-code text-2xl py-4"
|
||||
}, verificationCode)
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'sas-input-wrap',
|
||||
className: "space-y-2"
|
||||
}, [
|
||||
React.createElement('label', {
|
||||
key: 'sas-label',
|
||||
className: "block text-sm text-secondary"
|
||||
}, "Enter the verified code"),
|
||||
React.createElement('input', {
|
||||
key: 'sas-input',
|
||||
type: 'text',
|
||||
value: sasInput,
|
||||
onChange: (event) => {
|
||||
setSasInput(event.target.value.toUpperCase());
|
||||
if (error) setError('');
|
||||
},
|
||||
autoFocus: true,
|
||||
autoComplete: 'off',
|
||||
spellCheck: false,
|
||||
inputMode: 'text',
|
||||
disabled: localConfirmed,
|
||||
placeholder: verificationCode ? 'Type code here' : 'Waiting for code…',
|
||||
className: "w-full rounded-lg border border-purple-500/30 bg-black/20 px-4 py-3 text-center text-xl tracking-[0.3em] text-primary uppercase focus:border-purple-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60",
|
||||
style: { fontFamily: 'monospace', textTransform: 'uppercase' }
|
||||
}),
|
||||
error && React.createElement('p', {
|
||||
key: 'sas-error',
|
||||
className: "text-sm text-red-400"
|
||||
}, error)
|
||||
]),
|
||||
// Verification status indicators
|
||||
React.createElement('div', {
|
||||
key: 'verification-status',
|
||||
@@ -142,14 +197,14 @@
|
||||
}, [
|
||||
React.createElement('button', {
|
||||
key: 'confirm',
|
||||
onClick: onConfirm,
|
||||
disabled: localConfirmed,
|
||||
className: `flex-1 py-3 px-4 rounded-lg font-medium transition-all duration-200 ${localConfirmed ? 'bg-gray-500/20 text-gray-400 cursor-not-allowed' : 'btn-verify text-white'}`
|
||||
onClick: handleConfirm,
|
||||
disabled: !canConfirm,
|
||||
className: `flex-1 py-3 px-4 rounded-lg font-medium transition-all duration-200 ${!canConfirm ? 'bg-gray-500/20 text-gray-400 cursor-not-allowed' : 'btn-verify text-white'}`
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: `fas ${localConfirmed ? 'fa-check-circle' : 'fa-check'} mr-2`
|
||||
}),
|
||||
localConfirmed ? 'Confirmed' : 'The codes match'
|
||||
localConfirmed ? 'Confirmed' : 'Confirm code'
|
||||
]),
|
||||
React.createElement('button', {
|
||||
key: 'reject',
|
||||
@@ -283,7 +338,9 @@
|
||||
markAnswerCreated,
|
||||
notificationIntegrationRef,
|
||||
isGeneratingKeys,
|
||||
handleCreateOffer
|
||||
handleCreateOffer,
|
||||
relayOnlyMode,
|
||||
setRelayOnlyMode
|
||||
}) => {
|
||||
const [mode, setMode] = React.useState('select');
|
||||
const [notificationPermissionRequested, setNotificationPermissionRequested] = React.useState(false);
|
||||
@@ -294,12 +351,12 @@
|
||||
onClearData();
|
||||
};
|
||||
|
||||
const handleVerificationConfirm = () => {
|
||||
onVerifyConnection(true);
|
||||
const handleVerificationConfirm = (userCode) => {
|
||||
return onVerifyConnection(userCode);
|
||||
};
|
||||
|
||||
const handleVerificationReject = () => {
|
||||
onVerifyConnection(false);
|
||||
onVerifyConnection(null, false);
|
||||
};
|
||||
|
||||
// Request notification permission on first user interaction
|
||||
@@ -449,6 +506,28 @@
|
||||
className: "text-secondary max-w-2xl mx-auto"
|
||||
}, "Choose a connection method for a secure channel with ECDH encryption and Perfect Forward Secrecy.")
|
||||
]),
|
||||
React.createElement('label', {
|
||||
key: 'privacy-mode',
|
||||
className: "mb-6 mx-auto flex max-w-2xl items-start gap-3 rounded-xl border border-purple-500/20 bg-purple-500/10 p-4 text-left"
|
||||
}, [
|
||||
React.createElement('input', {
|
||||
key: 'input',
|
||||
type: 'checkbox',
|
||||
checked: relayOnlyMode,
|
||||
onChange: (event) => setRelayOnlyMode(event.target.checked),
|
||||
className: "mt-1"
|
||||
}),
|
||||
React.createElement('span', { key: 'copy' }, [
|
||||
React.createElement('span', {
|
||||
key: 'title',
|
||||
className: "block text-sm font-medium text-primary"
|
||||
}, 'Privacy mode: relay-only WebRTC'),
|
||||
React.createElement('span', {
|
||||
key: 'desc',
|
||||
className: "block text-sm text-secondary"
|
||||
}, 'Uses TURN relay-only when configured. Without TURN, direct WebRTC may expose IP addresses and relay-only connections cannot start.')
|
||||
])
|
||||
]),
|
||||
|
||||
React.createElement('div', {
|
||||
key: 'options',
|
||||
@@ -1464,6 +1543,9 @@
|
||||
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
const [connectionStatus, setConnectionStatus] = React.useState('disconnected');
|
||||
const [relayOnlyMode, setRelayOnlyMode] = React.useState(() => {
|
||||
try { return localStorage.getItem('securebit_relay_only_mode') === 'true'; } catch { return false; }
|
||||
});
|
||||
|
||||
// Moved scrollToBottom logic to be available globally
|
||||
const [messageInput, setMessageInput] = React.useState('');
|
||||
@@ -1680,6 +1762,13 @@
|
||||
|
||||
// Create scroll function using global helper
|
||||
const scrollToBottom = createScrollToBottomFunction(chatMessagesRef);
|
||||
|
||||
React.useEffect(() => {
|
||||
try { localStorage.setItem('securebit_relay_only_mode', String(relayOnlyMode)); } catch {}
|
||||
if (webrtcManagerRef.current?._config?.webrtc) {
|
||||
webrtcManagerRef.current._config.webrtc.relayOnly = relayOnlyMode;
|
||||
}
|
||||
}, [relayOnlyMode]);
|
||||
|
||||
// Auto-scroll when messages change
|
||||
React.useEffect(() => {
|
||||
@@ -1909,7 +1998,13 @@
|
||||
handleKeyExchange,
|
||||
handleVerificationRequired,
|
||||
handleAnswerError,
|
||||
handleVerificationStateChange
|
||||
handleVerificationStateChange,
|
||||
{
|
||||
webrtc: {
|
||||
relayOnly: relayOnlyMode,
|
||||
iceServers: Array.isArray(window.SECUREBIT_ICE_SERVERS) ? window.SECUREBIT_ICE_SERVERS : undefined
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Initialize notification integration if permission was already granted
|
||||
@@ -1926,7 +2021,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(' SecureBit.chat Enhanced Security Edition v4.7.56 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
|
||||
handleMessage(' SecureBit.chat Enhanced Security Edition v4.8.5 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
|
||||
|
||||
const handleBeforeUnload = (event) => {
|
||||
if (event.type === 'beforeunload' && !isTabSwitching) {
|
||||
@@ -3226,9 +3321,9 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyConnection = async (isValid) => {
|
||||
const handleVerifyConnection = async (userCode, isValid = true) => {
|
||||
if (isValid) {
|
||||
webrtcManagerRef.current.confirmVerification();
|
||||
webrtcManagerRef.current.confirmVerification(userCode);
|
||||
// Mark local verification as confirmed
|
||||
setLocalVerificationConfirmed(true);
|
||||
|
||||
@@ -3666,7 +3761,9 @@
|
||||
markAnswerCreated: markAnswerCreated,
|
||||
notificationIntegrationRef: notificationIntegrationRef,
|
||||
isGeneratingKeys: isGeneratingKeys,
|
||||
handleCreateOffer: handleCreateOffer
|
||||
handleCreateOffer: handleCreateOffer,
|
||||
relayOnlyMode: relayOnlyMode,
|
||||
setRelayOnlyMode: setRelayOnlyMode
|
||||
})
|
||||
),
|
||||
|
||||
@@ -3810,4 +3907,4 @@
|
||||
ReactDOM.render(AppWithUpdateChecker, document.getElementById('root'));
|
||||
} else {
|
||||
ReactDOM.render(React.createElement(EnhancedSecureP2PChat), document.getElementById('root'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user