working!
This commit is contained in:
36
src/app.py
36
src/app.py
@@ -2,8 +2,6 @@ import eventlet
|
|||||||
eventlet.monkey_patch()
|
eventlet.monkey_patch()
|
||||||
from flask import Flask, make_response, render_template, request, jsonify
|
from flask import Flask, make_response, render_template, request, jsonify
|
||||||
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
|
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
|
||||||
from flask_limiter import Limiter
|
|
||||||
from flask_limiter.util import get_remote_address
|
|
||||||
import collections
|
import collections
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -23,7 +21,8 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
socketio = SocketIO(app)
|
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')
|
||||||
|
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')
|
||||||
|
|
||||||
# Security constants
|
# Security constants
|
||||||
MAX_MESSAGES = 256
|
MAX_MESSAGES = 256
|
||||||
@@ -33,8 +32,6 @@ MAX_ROOMS_PER_IP = 5
|
|||||||
MAX_USERS_PER_ROOM = 50
|
MAX_USERS_PER_ROOM = 50
|
||||||
ROOM_CLEANUP_INTERVAL = 3600 # 1 hour
|
ROOM_CLEANUP_INTERVAL = 3600 # 1 hour
|
||||||
USER_SESSION_TIMEOUT = 3600 # 1 hour
|
USER_SESSION_TIMEOUT = 3600 # 1 hour
|
||||||
RATE_LIMIT_MESSAGE = "10 per minute"
|
|
||||||
RATE_LIMIT_ROOM_JOIN = "5 per minute"
|
|
||||||
|
|
||||||
# In-memory storage with enhanced security
|
# In-memory storage with enhanced security
|
||||||
chat_rooms = {}
|
chat_rooms = {}
|
||||||
@@ -45,7 +42,6 @@ room_creation_times = {}
|
|||||||
ip_room_count = {} # Track rooms created per IP
|
ip_room_count = {} # Track rooms created per IP
|
||||||
failed_password_attempts = {} # Track failed password attempts
|
failed_password_attempts = {} # Track failed password attempts
|
||||||
message_hashes = {} # Store message hashes for duplicate detection
|
message_hashes = {} # Store message hashes for duplicate detection
|
||||||
room_session_keys = {} # Store session keys for each room
|
|
||||||
|
|
||||||
class CircularMessageBuffer:
|
class CircularMessageBuffer:
|
||||||
def __init__(self, max_size=MAX_MESSAGES):
|
def __init__(self, max_size=MAX_MESSAGES):
|
||||||
@@ -154,7 +150,6 @@ def cleanup_room(room_id):
|
|||||||
room_passwords.pop(room_id, None)
|
room_passwords.pop(room_id, None)
|
||||||
room_creation_times.pop(room_id, None)
|
room_creation_times.pop(room_id, None)
|
||||||
message_hashes.pop(room_id, None)
|
message_hashes.pop(room_id, None)
|
||||||
room_session_keys.pop(room_id, None) # Clean up session keys
|
|
||||||
|
|
||||||
def get_client_ip():
|
def get_client_ip():
|
||||||
"""Get real client IP address"""
|
"""Get real client IP address"""
|
||||||
@@ -297,7 +292,6 @@ def handle_join_room(data):
|
|||||||
room_keys[room_id] = {}
|
room_keys[room_id] = {}
|
||||||
room_creation_times[room_id] = time.time()
|
room_creation_times[room_id] = time.time()
|
||||||
message_hashes[room_id] = set()
|
message_hashes[room_id] = set()
|
||||||
room_session_keys[room_id] = None # Initialize session key storage
|
|
||||||
|
|
||||||
# Store hashed password if provided
|
# Store hashed password if provided
|
||||||
if password:
|
if password:
|
||||||
@@ -499,25 +493,6 @@ def handle_share_session_key(data):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in share_session_key: {str(e)}")
|
logger.error(f"Error in share_session_key: {str(e)}")
|
||||||
|
|
||||||
@socketio.on('key_exchange')
|
|
||||||
@require_valid_session
|
|
||||||
def handle_key_exchange(data):
|
|
||||||
"""Legacy key exchange handler - redirects to share_session_key"""
|
|
||||||
try:
|
|
||||||
# Map old format to new format
|
|
||||||
room_id = data.get('room_id', '')
|
|
||||||
target_user = data.get('target_user', '')
|
|
||||||
encrypted_key = data.get('encrypted_key', '')
|
|
||||||
|
|
||||||
if room_id and target_user and encrypted_key:
|
|
||||||
handle_share_session_key({
|
|
||||||
'room_id': room_id,
|
|
||||||
'target_user_id': target_user,
|
|
||||||
'encrypted_key': encrypted_key
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error in legacy key_exchange: {str(e)}")
|
|
||||||
|
|
||||||
# Background cleanup task
|
# Background cleanup task
|
||||||
def start_cleanup_task():
|
def start_cleanup_task():
|
||||||
def cleanup_worker():
|
def cleanup_worker():
|
||||||
@@ -532,10 +507,6 @@ def start_cleanup_task():
|
|||||||
cleanup_thread.start()
|
cleanup_thread.start()
|
||||||
|
|
||||||
# Error handlers
|
# Error handlers
|
||||||
@app.errorhandler(429)
|
|
||||||
def ratelimit_handler(e):
|
|
||||||
return jsonify({'error': 'Rate limit exceeded'}), 429
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(e):
|
def not_found(e):
|
||||||
return jsonify({'error': 'Not found'}), 404
|
return jsonify({'error': 'Not found'}), 404
|
||||||
@@ -547,7 +518,8 @@ def internal_error(e):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
socketio.run(app, debug=True, allow_unsafe_werkzeug=True)
|
start_cleanup_task()
|
||||||
|
socketio.run(app, debug=True, host='0.0.0.0', port=5000, allow_unsafe_werkzeug=True)
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
# Suppress noisy broken pipe errors (client disconnects)
|
# Suppress noisy broken pipe errors (client disconnects)
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>Secure Chat Platform</title>
|
<title>Secure Chat Platform</title>
|
||||||
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
||||||
<meta http-equiv="X-Frame-Options" content="DENY">
|
<meta http-equiv="X-Frame-Options" content="DENY">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@3.1.3/dist/socket.io.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.2/dist/socket.io.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
/* Enhanced CSS with security considerations */
|
/* Enhanced CSS with security considerations */
|
||||||
* {
|
* {
|
||||||
@@ -575,9 +575,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Security-hardened JavaScript with improved key exchange
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Content Security Policy enforcement
|
// Content Security Policy enforcement
|
||||||
if (!window.crypto || !window.crypto.subtle) {
|
if (!window.crypto || !window.crypto.subtle) {
|
||||||
alert('This browser does not support required cryptographic features. Please use a modern browser.');
|
alert('This browser does not support required cryptographic features. Please use a modern browser.');
|
||||||
@@ -648,7 +645,14 @@
|
|||||||
async function initializeApp() {
|
async function initializeApp() {
|
||||||
try {
|
try {
|
||||||
console.log("Initializing secure chat application");
|
console.log("Initializing secure chat application");
|
||||||
|
|
||||||
|
// Initialize socket connection
|
||||||
socket = io({
|
socket = io({
|
||||||
|
transports: ['websocket', 'polling'],
|
||||||
|
reconnection: true,
|
||||||
|
reconnectionAttempts: MAX_RECONNECT_ATTEMPTS,
|
||||||
|
reconnectionDelay: 1000,
|
||||||
|
reconnectionDelayMax: 5000,
|
||||||
timeout: 10000
|
timeout: 10000
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -678,13 +682,6 @@
|
|||||||
|
|
||||||
const securityIndicator = document.getElementById('securityIndicator');
|
const securityIndicator = document.getElementById('securityIndicator');
|
||||||
securityIndicator.style.display = 'block';
|
securityIndicator.style.display = 'block';
|
||||||
|
|
||||||
setInterval(async function() {
|
|
||||||
if (currentRoom && sessionKey) {
|
|
||||||
console.log('Rotating session key...');
|
|
||||||
await rotateSessionKey();
|
|
||||||
}
|
|
||||||
}, 30 * 60 * 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSensitiveData() {
|
function clearSensitiveData() {
|
||||||
@@ -705,7 +702,7 @@
|
|||||||
keyPair = await window.crypto.subtle.generateKey(
|
keyPair = await window.crypto.subtle.generateKey(
|
||||||
{
|
{
|
||||||
name: "RSA-OAEP",
|
name: "RSA-OAEP",
|
||||||
modulusLength: 4096,
|
modulusLength: 2048, // Reduced from 4096 for better performance
|
||||||
publicExponent: new Uint8Array([1, 0, 1]),
|
publicExponent: new Uint8Array([1, 0, 1]),
|
||||||
hash: "SHA-256"
|
hash: "SHA-256"
|
||||||
},
|
},
|
||||||
@@ -739,38 +736,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rotateSessionKey() {
|
|
||||||
if (!currentRoom || !roomUsers || Object.keys(roomUsers).length <= 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await generateSessionKey();
|
|
||||||
|
|
||||||
for (const [userId, publicKeyString] of Object.entries(roomUsers)) {
|
|
||||||
if (userId !== currentUserId) {
|
|
||||||
try {
|
|
||||||
const publicKey = await importPublicKey(publicKeyString);
|
|
||||||
const encryptedKey = await encryptSessionKey(sessionKey, publicKey);
|
|
||||||
|
|
||||||
socket.emit('share_session_key', {
|
|
||||||
room_id: currentRoom,
|
|
||||||
target_user_id: userId,
|
|
||||||
encrypted_key: encryptedKey
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to share rotated key with user ${userId}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addSystemMessage("🔄 Session key rotated for enhanced security");
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to rotate session key:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exportPublicKey(key) {
|
async function exportPublicKey(key) {
|
||||||
try {
|
try {
|
||||||
const exported = await window.crypto.subtle.exportKey("spki", key);
|
const exported = await window.crypto.subtle.exportKey("spki", key);
|
||||||
@@ -1508,26 +1473,6 @@
|
|||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
// Memory cleanup
|
|
||||||
setInterval(() => {
|
|
||||||
if (typeof gc === 'function') {
|
|
||||||
gc();
|
|
||||||
}
|
|
||||||
}, 300000);
|
|
||||||
|
|
||||||
let windowHasFocus = true;
|
|
||||||
|
|
||||||
window.addEventListener('focus', () => {
|
|
||||||
windowHasFocus = true;
|
|
||||||
if (!isConnected && socket) {
|
|
||||||
socket.connect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('blur', () => {
|
|
||||||
windowHasFocus = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent clickjacking
|
// Prevent clickjacking
|
||||||
if (top !== self) {
|
if (top !== self) {
|
||||||
top.location = self.location;
|
top.location = self.location;
|
||||||
|
|||||||
Reference in New Issue
Block a user