ok
Browse files- app.js +15 -12
- public/index.html +1 -2
- public/js/main.js +4 -8
- routes/auth.js +7 -5
- routes/command.js +5 -22
- utils/logger.js +5 -2
app.js
CHANGED
@@ -1,11 +1,10 @@
|
|
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,11 +30,12 @@ if (process.env.NODE_ENV !== 'production') {
|
|
31 |
|
32 |
// 安全中间件
|
33 |
app.use(helmet());
|
|
|
34 |
|
35 |
// 速率限制
|
36 |
const limiter = rateLimit({
|
37 |
-
windowMs: 15 * 60 * 1000,
|
38 |
-
max: 100
|
39 |
});
|
40 |
app.use('/api', limiter);
|
41 |
|
@@ -78,16 +78,19 @@ app.use((err, req, res, next) => {
|
|
78 |
res.status(500).json({ error: '服务器内部错误' });
|
79 |
});
|
80 |
|
81 |
-
//
|
82 |
-
const
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
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 |
-
}
|
|
|
|
|
|
|
|
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 |
|
9 |
const app = express();
|
10 |
const port = process.env.PORT || 7860;
|
|
|
30 |
|
31 |
// 安全中间件
|
32 |
app.use(helmet());
|
33 |
+
app.use(cors());
|
34 |
|
35 |
// 速率限制
|
36 |
const limiter = rateLimit({
|
37 |
+
windowMs: 15 * 60 * 1000,
|
38 |
+
max: 100
|
39 |
});
|
40 |
app.use('/api', limiter);
|
41 |
|
|
|
78 |
res.status(500).json({ error: '服务器内部错误' });
|
79 |
});
|
80 |
|
81 |
+
// 启动服务器
|
82 |
+
const startServer = async () => {
|
83 |
+
try {
|
84 |
+
const historyFilePath = path.join(__dirname, 'data', 'command_history.json');
|
85 |
+
await fs.promises.access(historyFilePath).catch(() => fs.promises.writeFile(historyFilePath, '[]'));
|
86 |
+
|
87 |
app.listen(port, () => {
|
88 |
logger.info(`Web 命令执行应用正在监听 http://localhost:${port}`);
|
89 |
});
|
90 |
+
} catch (err) {
|
|
|
91 |
logger.error('无法创建命令历史文件:', err);
|
92 |
process.exit(1);
|
93 |
+
}
|
94 |
+
};
|
95 |
+
|
96 |
+
startServer();
|
public/index.html
CHANGED
@@ -7,9 +7,8 @@
|
|
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 |
}
|
|
|
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 |
}
|
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;
|
15 |
-
if (!command
|
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 |
-
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 |
-
loadCommandHistory();
|
87 |
} else {
|
88 |
alert('登录失败: ' + data.error);
|
89 |
}
|
@@ -94,8 +94,6 @@ async function login() {
|
|
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,7 +103,5 @@ function checkLoginStatus() {
|
|
105 |
}
|
106 |
}
|
107 |
|
108 |
-
// 在页面加载时调用此函数
|
109 |
window.addEventListener('load', checkLoginStatus);
|
110 |
-
|
111 |
document.getElementById('executeButton').addEventListener('click', executeCommand);
|
|
|
11 |
});
|
12 |
|
13 |
async function executeCommand() {
|
14 |
+
const command = commandInput.value.trim();
|
15 |
+
if (!command) return;
|
16 |
|
17 |
showLoading(true);
|
18 |
output.textContent = '正在执行命令...';
|
|
|
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 |
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 |
|
95 |
document.getElementById('loginButton').addEventListener('click', login);
|
96 |
|
|
|
|
|
97 |
function checkLoginStatus() {
|
98 |
const token = localStorage.getItem('token');
|
99 |
if (token) {
|
|
|
103 |
}
|
104 |
}
|
105 |
|
|
|
106 |
window.addEventListener('load', checkLoginStatus);
|
|
|
107 |
document.getElementById('executeButton').addEventListener('click', executeCommand);
|
routes/auth.js
CHANGED
@@ -1,23 +1,25 @@
|
|
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
|
18 |
|
19 |
-
if (user) {
|
20 |
-
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn:
|
21 |
res.json({ token });
|
22 |
} else {
|
23 |
res.status(401).json({ error: '无效的用户名或密码' });
|
|
|
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: '无效的用户名或密码' });
|
routes/command.js
CHANGED
@@ -6,21 +6,13 @@ const router = express.Router();
|
|
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 |
-
|
18 |
-
|
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,7 +24,6 @@ router.post('/execute', async (req, res) => {
|
|
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,19 +35,11 @@ router.post('/execute', async (req, res) => {
|
|
44 |
return res.status(500).json({ error: error.message });
|
45 |
}
|
46 |
|
47 |
-
// 记录命令历史
|
48 |
try {
|
49 |
-
|
50 |
-
|
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 |
-
|
59 |
-
|
60 |
await fs.writeFile(historyFilePath, JSON.stringify(history, null, 2));
|
61 |
} catch (writeError) {
|
62 |
logger.error('写入命令历史失败:', writeError);
|
|
|
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 |
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 |
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);
|
utils/logger.js
CHANGED
@@ -1,4 +1,7 @@
|
|
1 |
const winston = require('winston');
|
|
|
|
|
|
|
2 |
|
3 |
const logger = winston.createLogger({
|
4 |
level: process.env.LOG_LEVEL || 'info',
|
@@ -7,8 +10,8 @@ const logger = winston.createLogger({
|
|
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 |
|
|
|
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 |
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 |
|