-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.js
185 lines (155 loc) · 4.7 KB
/
server.js
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
const fs = require("fs");
const https = require("https");
const http = require("http");
const WebSocket = require("ws");
const { exec, spawn } = require("child_process");
const jwt = require("jsonwebtoken");
const path = require("path");
// Configuration
const SECRET_KEY = process.env.SECRET_KEY;
const USE_SSL = process.env.USE_SSL === "true";
const PORT = 8080;
const CERT_PATH = "./certs/server.crt"; // Public certificate path
const KEY_PATH = "./certs/server.key"; // Private key path
const MAX_MESSAGES_PER_SECOND = 5; // Rate limiting configuration
const MC_NAME = process.env.MC_NAME || "minecraft";
if (!SECRET_KEY) {
throw new Error("SECRET_KEY is not defined in docker-compose.yml");
}
// HTTP request handler for serving the certificate
function handleRequest(req, res) {
if (req.method === "GET" && req.url === "/getcert") {
const certFilePath = path.join(__dirname, CERT_PATH);
res.writeHead(200, {
"Content-Type": "application/x-x509-ca-cert",
"Content-Disposition": 'attachment; filename="server.crt"',
});
fs.createReadStream(certFilePath).pipe(res);
} else {
res.writeHead(404);
res.end("Not Found");
}
}
// JWT authentication
function authenticate(token) {
try {
return jwt.verify(token, SECRET_KEY);
} catch (e) {
console.error("Authentication error:", e.message);
return null;
}
}
// Command execution
function executeCommand(command) {
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Execution error: ${error.message}`);
return;
}
if (stderr) {
console.error(`Command error: ${stderr}`);
return;
}
console.log(`Command output: ${stdout}`);
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(stdout);
}
});
});
}
// Restart server command
function restartServer() {
console.log("Restarting the Minecraft server...");
executeCommand("docker restart ${MC_NAME}");
}
// Validate and sanitize WebSocket messages to prevent command injection
function validateMessage(message) {
const validCommandPattern = /^[a-zA-Z0-9_\- ]+$/;
return validCommandPattern.test(message);
}
// Create server with or without SSL
let server;
let useSSL = USE_SSL;
if (useSSL) {
try {
fs.accessSync(CERT_PATH, fs.constants.F_OK);
fs.accessSync(KEY_PATH, fs.constants.F_OK);
} catch (err) {
console.warn("=================================================================");
console.warn("WARNING: SSL certificates not found. Starting server without SSL.");
console.warn("=================================================================");
useSSL = false;
}
}
if (useSSL) {
server = https.createServer(
{
cert: fs.readFileSync(CERT_PATH),
key: fs.readFileSync(KEY_PATH),
},
handleRequest
);
} else {
server = http.createServer();
}
// WebSocket server setup
const wss = new WebSocket.Server({ server });
server.listen(PORT, () => {
console.log(`WebSocket server is listening on port ${PORT}${useSSL ? " with SSL enabled" : " with SSL disabled"}`);
});
wss.on("connection", (ws, request) => {
console.log("New client connected");
const url = new URL(request.url, `${useSSL ? "wss" : "ws"}://${request.headers.host}`);
const token = url.searchParams.get("token");
const user = authenticate(token);
if (!user) {
ws.close(4001, "Authentication failed");
return;
}
const logProcess = spawn("docker", ["logs", "-f", MC_NAME]);
logProcess.stdout.on("data", (data) => {
ws.send(data.toString());
});
logProcess.stderr.on("data", () => {
ws.send("Error occurred.");
});
logProcess.on("close", (code) => {
console.log(`logProcess exited with code ${code}`);
});
let messageCount = 0;
let startTime = Date.now();
ws.on("message", (message) => {
const msgString = message.toString();
const currentTime = Date.now();
// Reset message count every second
if (currentTime - startTime > 1000) {
messageCount = 0;
startTime = currentTime;
}
// Rate limiting
if (messageCount >= MAX_MESSAGES_PER_SECOND) {
ws.send("Rate limit exceeded. Please slow down.");
return;
}
messageCount++;
console.log(`Received message: ${msgString}`);
if (!validateMessage(msgString)) {
ws.send("Invalid input.");
return;
}
if (msgString === "admincraft restart-server") {
restartServer();
} else {
executeCommand(`docker exec ${MC_NAME} send-command ${msgString}`);
}
});
ws.on("close", () => {
console.log("Client disconnected");
logProcess.kill();
});
ws.on("error", (error) => {
console.error("WebSocket error:", error.message);
});
ws.send(`${user.userId} connected`);
});