-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathserver.js
More file actions
133 lines (113 loc) · 4.1 KB
/
Copy pathserver.js
File metadata and controls
133 lines (113 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* @fileoverview Production server for QryptChat
* Integrates SvelteKit with WebSocket server for real-time chat functionality
*/
import { createServer } from 'node:http';
import { handler } from './build/handler.js';
import { WebSocketServer } from 'ws';
import { ChatWebSocketServer } from './src/lib/websocket/server.js';
import { messageCleanupService } from './src/lib/services/message-cleanup-service.js';
// Create HTTP server that delegates all non-WS requests to SvelteKit
const server = createServer((req, res) => {
// SvelteKit handles all HTTP requests
handler(req, res);
});
// Create WebSocket server in "noServer" mode for manual upgrade handling
const wss = new WebSocketServer({ noServer: true });
// Create our chat WebSocket server instance
const chatServer = new ChatWebSocketServer({
wss, // Pass the WebSocket server instance
noListen: true // Don't create its own server
});
// Per-IP WebSocket connection rate limiting
const wsConnectionCounts = new Map(); // ip -> { count, firstSeen }
const WS_MAX_CONNECTIONS_PER_IP = 10;
const WS_RATE_WINDOW_MS = 60 * 1000; // 1 minute
function getClientIp(request) {
// SECURITY: never trust the leftmost X-Forwarded-For entry — it is client
// supplied and lets an attacker forge a new "IP" per connection to bypass the
// per-IP WebSocket rate limit below. Mirror the trusted-proxy logic in
// src/lib/server/rate-limiter.js: only the rightmost `TRUSTED_PROXY_COUNT`
// hops are appended by infrastructure we control and cannot be spoofed.
const trustedProxyCount = parseInt(process.env.TRUSTED_PROXY_COUNT ?? '0', 10);
if (trustedProxyCount > 0) {
const xff = request.headers['x-forwarded-for'];
if (xff) {
const parts = xff.split(',').map((s) => s.trim()).filter(Boolean);
if (parts.length >= trustedProxyCount) {
return parts[parts.length - trustedProxyCount];
}
}
}
// Default / direct-internet deployment: rely on X-Real-IP (set by a trusted
// proxy) then the raw TCP peer address, which cannot be spoofed via headers.
return request.headers['x-real-ip'] || request.socket?.remoteAddress || 'unknown';
}
// Clean up stale entries every 5 minutes
setInterval(() => {
const now = Date.now();
for (const [ip, data] of wsConnectionCounts) {
if (now - data.firstSeen > WS_RATE_WINDOW_MS) {
wsConnectionCounts.delete(ip);
}
}
}, 5 * 60 * 1000);
// Handle WebSocket upgrade requests with per-IP rate limiting
server.on('upgrade', (request, socket, head) => {
// Only upgrade requests to /ws path
if (request.url !== '/ws') {
socket.destroy();
return;
}
// Rate limit by IP
const ip = getClientIp(request);
const now = Date.now();
const record = wsConnectionCounts.get(ip);
if (record) {
if (now - record.firstSeen > WS_RATE_WINDOW_MS) {
// Window expired, reset
wsConnectionCounts.set(ip, { count: 1, firstSeen: now });
} else if (record.count >= WS_MAX_CONNECTIONS_PER_IP) {
console.warn(`⚠️ WebSocket rate limit exceeded for IP: ${ip}`);
socket.write('HTTP/1.1 429 Too Many Requests\r\n\r\n');
socket.destroy();
return;
} else {
record.count++;
}
} else {
wsConnectionCounts.set(ip, { count: 1, firstSeen: now });
}
// Handle the upgrade using our WebSocket server
wss.handleUpgrade(request, socket, head, (ws) => {
// Use our chat server's connection handler
chatServer.handleConnection(ws, request);
});
});
// Graceful shutdown handling
const shutdown = () => {
console.log('Shutting down server...');
// Stop message cleanup service
messageCleanupService.stop();
// Close WebSocket server
wss.close(() => {
console.log('WebSocket server closed');
});
// Close HTTP server
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
// Start the server
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`🚀 QryptChat server running on http://localhost:${PORT}`);
console.log(`📡 WebSocket endpoint available at ws://localhost:${PORT}/ws`);
// Start message cleanup service after server is running
setTimeout(() => {
messageCleanupService.start();
}, 2000);
});