aigems commited on
Commit
10c367b
·
1 Parent(s): 4dd0e28
Files changed (8) hide show
  1. Dockerfile +1 -5
  2. app.js +12 -16
  3. package.json +9 -11
  4. public/index.html +2 -1
  5. public/js/main.js +8 -4
  6. routes/auth.js +5 -7
  7. routes/command.js +22 -5
  8. utils/logger.js +2 -5
Dockerfile CHANGED
@@ -11,14 +11,10 @@ RUN npm install
11
 
12
  COPY . .
13
 
14
- RUN mkdir -p /app/data /app/logs && \
15
- chown -R node:node /app
16
-
17
- USER node
18
 
19
  EXPOSE 7860
20
 
21
- ENV NODE_ENV=production
22
  ENV ADMIN_USERNAME=${ADMIN_USERNAME}
23
  ENV ADMIN_PASSWORD=${ADMIN_PASSWORD}
24
 
 
11
 
12
  COPY . .
13
 
14
+ RUN mkdir -p /app/data && chown -R node:node /app /app/data
 
 
 
15
 
16
  EXPOSE 7860
17
 
 
18
  ENV ADMIN_USERNAME=${ADMIN_USERNAME}
19
  ENV ADMIN_PASSWORD=${ADMIN_PASSWORD}
20
 
app.js CHANGED
@@ -1,11 +1,11 @@
1
  const express = require('express');
2
  const winston = require('winston');
 
3
  const path = require('path');
4
  const rateLimit = require('express-rate-limit');
 
5
  const helmet = require('helmet');
6
  const jwt = require('jsonwebtoken');
7
- const cors = require('cors');
8
- const fs = require('fs');
9
 
10
  const app = express();
11
  const port = process.env.PORT || 7860;
@@ -31,12 +31,11 @@ if (process.env.NODE_ENV !== 'production') {
31
 
32
  // 安全中间件
33
  app.use(helmet());
34
- app.use(cors());
35
 
36
  // 速率限制
37
  const limiter = rateLimit({
38
- windowMs: 15 * 60 * 1000,
39
- max: 100
40
  });
41
  app.use('/api', limiter);
42
 
@@ -79,19 +78,16 @@ app.use((err, req, res, next) => {
79
  res.status(500).json({ error: '服务器内部错误' });
80
  });
81
 
82
- // 启动服务器
83
- const startServer = async () => {
84
- try {
85
- const historyFilePath = path.join(__dirname, 'data', 'command_history.json');
86
- await fs.promises.access(historyFilePath).catch(() => fs.promises.writeFile(historyFilePath, '[]'));
87
-
88
  app.listen(port, () => {
89
  logger.info(`Web 命令执行应用正在监听 http://localhost:${port}`);
90
  });
91
- } catch (err) {
 
92
  logger.error('无法创建命令历史文件:', err);
93
  process.exit(1);
94
- }
95
- };
96
-
97
- startServer();
 
1
  const express = require('express');
2
  const winston = require('winston');
3
+ const fs = require('fs').promises;
4
  const path = require('path');
5
  const rateLimit = require('express-rate-limit');
6
+ const { promises: fsPromises } = require('fs');
7
  const helmet = require('helmet');
8
  const jwt = require('jsonwebtoken');
 
 
9
 
10
  const app = express();
11
  const port = process.env.PORT || 7860;
 
31
 
32
  // 安全中间件
33
  app.use(helmet());
 
34
 
35
  // 速率限制
36
  const limiter = rateLimit({
37
+ windowMs: 15 * 60 * 1000, // 15分钟
38
+ max: 100 // 每个IP限制100个请求
39
  });
40
  app.use('/api', limiter);
41
 
 
78
  res.status(500).json({ error: '服务器内部错误' });
79
  });
80
 
81
+ // 确保命令历史文件存在
82
+ const historyFilePath = path.join(__dirname, 'data', 'command_history.json');
83
+ fsPromises.access(historyFilePath)
84
+ .catch(() => fsPromises.writeFile(historyFilePath, '[]'))
85
+ .then(() => {
 
86
  app.listen(port, () => {
87
  logger.info(`Web 命令执行应用正在监听 http://localhost:${port}`);
88
  });
89
+ })
90
+ .catch(err => {
91
  logger.error('无法创建命令历史文件:', err);
92
  process.exit(1);
93
+ });
 
 
 
package.json CHANGED
@@ -9,20 +9,18 @@
9
  "test": "jest"
10
  },
11
  "dependencies": {
12
- "express": "^4.18.2",
13
- "winston": "^3.10.0",
14
- "helmet": "^7.0.0",
15
- "express-rate-limit": "^6.9.0",
16
- "jsonwebtoken": "^9.0.1",
17
- "cors": "^2.8.5",
18
- "bcrypt": "^5.1.0"
19
  },
20
  "devDependencies": {
21
- "nodemon": "^3.0.1",
22
- "eslint": "^8.47.0",
23
- "jest": "^29.6.2"
24
  },
25
  "engines": {
26
- "node": ">=18.0.0"
27
  }
28
  }
 
9
  "test": "jest"
10
  },
11
  "dependencies": {
12
+ "express": "^4.17.1",
13
+ "winston": "^3.3.3",
14
+ "helmet": "^4.6.0",
15
+ "express-rate-limit": "^5.3.0",
16
+ "jsonwebtoken": "^8.5.1"
 
 
17
  },
18
  "devDependencies": {
19
+ "nodemon": "^2.0.7",
20
+ "eslint": "^7.32.0",
21
+ "jest": "^27.0.6"
22
  },
23
  "engines": {
24
+ "node": ">=14.0.0"
25
  }
26
  }
public/index.html CHANGED
@@ -7,8 +7,9 @@
7
  <title>Web 命令执行</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xss.min.js"></script>
10
- <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
11
  <style>
 
 
12
  body {
13
  font-family: 'Noto Sans SC', sans-serif;
14
  }
 
7
  <title>Web 命令执行</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xss.min.js"></script>
 
10
  <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap');
12
+
13
  body {
14
  font-family: 'Noto Sans SC', sans-serif;
15
  }
public/js/main.js CHANGED
@@ -11,8 +11,8 @@ commandInput.addEventListener('keypress', function (event) {
11
  });
12
 
13
  async function executeCommand() {
14
- const command = commandInput.value.trim();
15
- if (!command) return;
16
 
17
  showLoading(true);
18
  output.textContent = '正在执行命令...';
@@ -30,7 +30,7 @@ async function executeCommand() {
30
  if (response.ok) {
31
  output.textContent = data.output || data.error;
32
  commandInput.value = '';
33
- await loadCommandHistory();
34
  } else {
35
  output.textContent = `错误: ${data.error}`;
36
  }
@@ -83,7 +83,7 @@ async function login() {
83
  localStorage.setItem('token', data.token);
84
  document.getElementById('loginForm').style.display = 'none';
85
  document.getElementById('commandInterface').style.display = 'block';
86
- await loadCommandHistory();
87
  } else {
88
  alert('登录失败: ' + data.error);
89
  }
@@ -94,6 +94,8 @@ async function login() {
94
 
95
  document.getElementById('loginButton').addEventListener('click', login);
96
 
 
 
97
  function checkLoginStatus() {
98
  const token = localStorage.getItem('token');
99
  if (token) {
@@ -103,5 +105,7 @@ function checkLoginStatus() {
103
  }
104
  }
105
 
 
106
  window.addEventListener('load', checkLoginStatus);
 
107
  document.getElementById('executeButton').addEventListener('click', executeCommand);
 
11
  });
12
 
13
  async function executeCommand() {
14
+ const command = commandInput.value;
15
+ if (!command.trim()) return;
16
 
17
  showLoading(true);
18
  output.textContent = '正在执行命令...';
 
30
  if (response.ok) {
31
  output.textContent = data.output || data.error;
32
  commandInput.value = '';
33
+ loadCommandHistory();
34
  } else {
35
  output.textContent = `错误: ${data.error}`;
36
  }
 
83
  localStorage.setItem('token', data.token);
84
  document.getElementById('loginForm').style.display = 'none';
85
  document.getElementById('commandInterface').style.display = 'block';
86
+ loadCommandHistory();
87
  } else {
88
  alert('登录失败: ' + data.error);
89
  }
 
94
 
95
  document.getElementById('loginButton').addEventListener('click', login);
96
 
97
+ loadCommandHistory();
98
+
99
  function checkLoginStatus() {
100
  const token = localStorage.getItem('token');
101
  if (token) {
 
105
  }
106
  }
107
 
108
+ // 在页面加载时调用此函数
109
  window.addEventListener('load', checkLoginStatus);
110
+
111
  document.getElementById('executeButton').addEventListener('click', executeCommand);
routes/auth.js CHANGED
@@ -1,25 +1,23 @@
1
  const express = require('express');
2
  const jwt = require('jsonwebtoken');
3
  const router = express.Router();
4
- const bcrypt = require('bcrypt');
5
 
6
  const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
7
- const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '1h';
8
 
9
  const users = [
10
  {
11
  id: 1,
12
  username: process.env.ADMIN_USERNAME,
13
- password: bcrypt.hashSync(process.env.ADMIN_PASSWORD, 10)
14
  }
15
  ];
16
 
17
- router.post('/login', async (req, res) => {
18
  const { username, password } = req.body;
19
- const user = users.find(u => u.username === username);
20
 
21
- if (user && await bcrypt.compare(password, user.password)) {
22
- const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
23
  res.json({ token });
24
  } else {
25
  res.status(401).json({ error: '无效的用户名或密码' });
 
1
  const express = require('express');
2
  const jwt = require('jsonwebtoken');
3
  const router = express.Router();
 
4
 
5
  const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
 
6
 
7
  const users = [
8
  {
9
  id: 1,
10
  username: process.env.ADMIN_USERNAME,
11
+ password: process.env.ADMIN_PASSWORD
12
  }
13
  ];
14
 
15
+ router.post('/login', (req, res) => {
16
  const { username, password } = req.body;
17
+ const user = users.find(u => u.username === username && u.password === password);
18
 
19
+ if (user) {
20
+ const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '1h' });
21
  res.json({ token });
22
  } else {
23
  res.status(401).json({ error: '无效的用户名或密码' });
routes/command.js CHANGED
@@ -6,13 +6,21 @@ const router = express.Router();
6
 
7
  const logger = require('../utils/logger');
8
 
 
9
  const allowedCommands = ['ls', 'pwd', 'whoami', 'date', 'echo', 'cat'];
 
 
10
  const historyFilePath = path.join(__dirname, '..', 'data', 'command_history.json');
11
 
12
  router.get('/command-history', async (req, res) => {
13
  try {
14
- const historyData = await fs.readFile(historyFilePath, 'utf-8');
15
- const history = JSON.parse(historyData);
 
 
 
 
 
16
  res.json(history);
17
  } catch (error) {
18
  logger.error('处理命令历史请求失败:', error);
@@ -24,6 +32,7 @@ router.post('/execute', async (req, res) => {
24
  const { command } = req.body;
25
  const baseCommand = command.split(' ')[0];
26
 
 
27
  // if (!allowedCommands.includes(baseCommand)) {
28
  // logger.warn(`用户 ${req.user.username} 尝试执行未授权的命令: ${command}`);
29
  // return res.status(403).json({ error: '未授权的命令' });
@@ -35,11 +44,19 @@ router.post('/execute', async (req, res) => {
35
  return res.status(500).json({ error: error.message });
36
  }
37
 
 
38
  try {
39
- const historyData = await fs.readFile(historyFilePath, 'utf-8');
40
- let history = JSON.parse(historyData);
 
 
 
 
 
 
41
  history.push({ command, timestamp: new Date().toISOString(), user: req.user.username });
42
- history = history.slice(-100); // 保留最近100条命令
 
43
  await fs.writeFile(historyFilePath, JSON.stringify(history, null, 2));
44
  } catch (writeError) {
45
  logger.error('写入命令历史失败:', writeError);
 
6
 
7
  const logger = require('../utils/logger');
8
 
9
+ // 命令白名单
10
  const allowedCommands = ['ls', 'pwd', 'whoami', 'date', 'echo', 'cat'];
11
+
12
+ // 历史命令文件路径
13
  const historyFilePath = path.join(__dirname, '..', 'data', 'command_history.json');
14
 
15
  router.get('/command-history', async (req, res) => {
16
  try {
17
+ let history = [];
18
+ try {
19
+ const historyData = await fs.readFile(historyFilePath, 'utf-8');
20
+ history = JSON.parse(historyData);
21
+ } catch (readError) {
22
+ logger.warn('读取命令历史失败,使用空数组:', readError);
23
+ }
24
  res.json(history);
25
  } catch (error) {
26
  logger.error('处理命令历史请求失败:', error);
 
32
  const { command } = req.body;
33
  const baseCommand = command.split(' ')[0];
34
 
35
+ // 白名单检查的部分
36
  // if (!allowedCommands.includes(baseCommand)) {
37
  // logger.warn(`用户 ${req.user.username} 尝试执行未授权的命令: ${command}`);
38
  // return res.status(403).json({ error: '未授权的命令' });
 
44
  return res.status(500).json({ error: error.message });
45
  }
46
 
47
+ // 记录命令历史
48
  try {
49
+ let history = [];
50
+ try {
51
+ const historyData = await fs.readFile(historyFilePath, 'utf-8');
52
+ history = JSON.parse(historyData);
53
+ } catch (readError) {
54
+ // 如果文件不存在或为空,使用空数组
55
+ }
56
+
57
  history.push({ command, timestamp: new Date().toISOString(), user: req.user.username });
58
+ if (history.length > 100) history.shift(); // 保留最近100条命令
59
+
60
  await fs.writeFile(historyFilePath, JSON.stringify(history, null, 2));
61
  } catch (writeError) {
62
  logger.error('写入命令历史失败:', writeError);
utils/logger.js CHANGED
@@ -1,7 +1,4 @@
1
  const winston = require('winston');
2
- const path = require('path');
3
-
4
- const logDir = path.join(__dirname, '..', 'logs');
5
 
6
  const logger = winston.createLogger({
7
  level: process.env.LOG_LEVEL || 'info',
@@ -10,8 +7,8 @@ const logger = winston.createLogger({
10
  winston.format.json()
11
  ),
12
  transports: [
13
- new winston.transports.File({ filename: path.join(logDir, 'error.log'), level: 'error' }),
14
- new winston.transports.File({ filename: path.join(logDir, 'combined.log') })
15
  ]
16
  });
17
 
 
1
  const winston = require('winston');
 
 
 
2
 
3
  const logger = winston.createLogger({
4
  level: process.env.LOG_LEVEL || 'info',
 
7
  winston.format.json()
8
  ),
9
  transports: [
10
+ new winston.transports.File({ filename: 'error.log', level: 'error' }),
11
+ new winston.transports.File({ filename: 'combined.log' })
12
  ]
13
  });
14