feat: production-ready release v1.0.0

- Integrated 'Koala-Bridge' for seamless web-to-extension communication.
- Implemented dedicated, minimalist invitation page (join.html).
- Added brute-force protection and RAM-cleanup to the relay server.
- Removed external fonts and trackers from the landing page (Privacy First).
- Unified header/footer design across all website pages.
- Added MIT License and comprehensive legal documentation (Impressum/Datenschutz).
This commit is contained in:
MacBook
2026-04-21 08:46:28 +02:00
parent 8b02593f40
commit 3e2e9ed7a5
15 changed files with 757 additions and 12 deletions
+54 -1
View File
@@ -42,8 +42,46 @@ function log(type, message, details = '') {
console.log(`[${timestamp}] [${type}] ${message}`, details);
}
// Rate Limiting Storage
// Rate Limiting & Security
const connectionCounts = new Map(); // ip -> { count, resetTime }
const failedAuthAttempts = new Map(); // Map<IP+RoomID, {count, lastAttempt}>
function checkAuthRate(ip, roomId) {
const key = `${ip}:${roomId}`;
const now = Date.now();
const record = failedAuthAttempts.get(key) || { count: 0, lastAttempt: 0 };
// Block for 15 mins if 5 fails in 2 mins
if (record.count >= 5 && (now - record.lastAttempt) < 15 * 60 * 1000) {
return false;
}
// Reset if last attempt was long ago
if ((now - record.lastAttempt) > 2 * 60 * 1000) {
record.count = 0;
}
return true;
}
function recordAuthFailure(ip, roomId) {
const key = `${ip}:${roomId}`;
const record = failedAuthAttempts.get(key) || { count: 0, lastAttempt: 0 };
record.count++;
record.lastAttempt = Date.now();
failedAuthAttempts.set(key, record);
}
// Periodically clean up old auth failure records (every hour)
setInterval(() => {
const now = Date.now();
for (const [key, record] of failedAuthAttempts.entries()) {
if (now - record.lastAttempt > 60 * 60 * 1000) {
failedAuthAttempts.delete(key);
}
}
}, 60 * 60 * 1000);
const eventCounts = new Map(); // socketId -> { count, resetTime }
function checkConnectionRate(ip) {
@@ -121,6 +159,12 @@ io.on('connection', (socket) => {
}
}
const ip = socket.handshake.address;
if (!checkAuthRate(ip, roomId)) {
socket.emit(EVENTS.ERROR, { message: "Too many failed attempts. Try again later." });
return;
}
let room = rooms.get(roomId);
if (!room) {
@@ -141,6 +185,7 @@ io.on('connection', (socket) => {
} else {
if (room.passwordHash) {
if (!password || !(await bcrypt.compare(password, room.passwordHash))) {
recordAuthFailure(ip, roomId);
socket.emit(EVENTS.ERROR, { message: "Invalid password" });
return;
}
@@ -192,6 +237,14 @@ io.on('connection', (socket) => {
});
});
socket.on(EVENTS.GET_ROOMS, () => {
const list = Array.from(rooms.entries()).map(([id, r]) => ({
id,
peerCount: r.peers.size
}));
socket.emit(EVENTS.ROOM_LIST, { rooms: list });
});
socket.on(EVENTS.LEAVE_ROOM, () => {
const mapping = socketToRoom.get(socket.id);
if (mapping) {