@chenpu17/tcpmapping 2.1.1 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -0
- package/README.md +113 -10
- package/bin/tcpmapping.js +476 -176
- package/docs/API.md +532 -0
- package/docs/screenshots/dashboard-en-dark.webp +0 -0
- package/docs/screenshots/dashboard-mobile-zh.webp +0 -0
- package/docs/screenshots/dashboard-zh-light.webp +0 -0
- package/docs/screenshots/login-zh-light.webp +0 -0
- package/docs//345/212/237/350/203/275/346/200/273/347/273/223.md +200 -0
- package/docs//345/256/211/345/205/250/351/205/215/347/275/256/350/257/264/346/230/216.md +154 -0
- package/package.json +6 -4
- package/public/common.js +411 -0
- package/public/dashboard.html +1624 -437
- package/public/login.html +430 -107
- package/server.js +1219 -464
- package/setup-password.js +137 -57
package/setup-password.js
CHANGED
|
@@ -20,32 +20,80 @@ const rl = readline.createInterface({
|
|
|
20
20
|
output: process.stdout
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
// 非 TTY 环境(如管道输入)使用行队列,避免缓冲区中的行被丢弃
|
|
24
|
+
const inputQueue = [];
|
|
25
|
+
const inputWaiters = [];
|
|
26
|
+
|
|
27
|
+
function readInputLine(prompt) {
|
|
28
|
+
if (!process.stdin.isTTY) {
|
|
29
|
+
process.stdout.write(prompt);
|
|
30
|
+
|
|
31
|
+
if (inputQueue.length > 0) {
|
|
32
|
+
return Promise.resolve(inputQueue.shift());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
inputWaiters.push(resolve);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
rl.question(prompt, resolve);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
30
44
|
|
|
45
|
+
if (!process.stdin.isTTY) {
|
|
46
|
+
rl.on('line', (line) => {
|
|
47
|
+
const waiter = inputWaiters.shift();
|
|
48
|
+
if (waiter) {
|
|
49
|
+
waiter(line);
|
|
50
|
+
} else {
|
|
51
|
+
inputQueue.push(line);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
rl.on('close', () => {
|
|
56
|
+
while (inputWaiters.length > 0) {
|
|
57
|
+
inputWaiters.shift()(null);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 默认密码策略
|
|
63
|
+
const DEFAULT_PASSWORD_POLICY = {
|
|
64
|
+
minLength: 8,
|
|
65
|
+
requireUppercase: true,
|
|
66
|
+
requireLowercase: true,
|
|
67
|
+
requireNumbers: true,
|
|
68
|
+
requireSpecialChars: false,
|
|
69
|
+
passwordExpireDays: 90
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 密码复杂度验证
|
|
73
|
+
function validatePassword(password, policy) {
|
|
74
|
+
const rules = { ...DEFAULT_PASSWORD_POLICY, ...(policy || {}) };
|
|
31
75
|
const errors = [];
|
|
32
|
-
|
|
33
|
-
if (password.length < minLength) {
|
|
34
|
-
errors.push(`密码长度至少需要${minLength}个字符`);
|
|
76
|
+
|
|
77
|
+
if (password.length < rules.minLength) {
|
|
78
|
+
errors.push(`密码长度至少需要${rules.minLength}个字符`);
|
|
35
79
|
}
|
|
36
|
-
|
|
37
|
-
if (
|
|
80
|
+
|
|
81
|
+
if (rules.requireUppercase && !/[A-Z]/.test(password)) {
|
|
38
82
|
errors.push('密码必须包含至少一个大写字母');
|
|
39
83
|
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
84
|
+
|
|
85
|
+
if (rules.requireLowercase && !/[a-z]/.test(password)) {
|
|
42
86
|
errors.push('密码必须包含至少一个小写字母');
|
|
43
87
|
}
|
|
44
|
-
|
|
45
|
-
if (
|
|
88
|
+
|
|
89
|
+
if (rules.requireNumbers && !/\d/.test(password)) {
|
|
46
90
|
errors.push('密码必须包含至少一个数字');
|
|
47
91
|
}
|
|
48
|
-
|
|
92
|
+
|
|
93
|
+
if (rules.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>_\-+=[\]\\/;'`~]/.test(password)) {
|
|
94
|
+
errors.push('密码必须包含至少一个特殊字符');
|
|
95
|
+
}
|
|
96
|
+
|
|
49
97
|
return {
|
|
50
98
|
isValid: errors.length === 0,
|
|
51
99
|
errors: errors
|
|
@@ -55,10 +103,11 @@ function validatePassword(password) {
|
|
|
55
103
|
// 获取用户输入(隐藏密码)
|
|
56
104
|
function getPassword(prompt) {
|
|
57
105
|
return new Promise((resolve) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
106
|
+
// 非 TTY 环境(如管道输入)无法使用原始模式,退回到普通输入
|
|
107
|
+
if (!process.stdin.isTTY) {
|
|
108
|
+
readInputLine(prompt).then(resolve);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
62
111
|
|
|
63
112
|
// Windows 兼容:使用简单的输入方式
|
|
64
113
|
// 在某些终端上密码可能可见,但这是最兼容的方式
|
|
@@ -84,10 +133,7 @@ function getPassword(prompt) {
|
|
|
84
133
|
|
|
85
134
|
// 回退方案:普通输入(密码可见)
|
|
86
135
|
console.log('(密码将可见)');
|
|
87
|
-
|
|
88
|
-
rl2.close();
|
|
89
|
-
resolve(answer);
|
|
90
|
-
});
|
|
136
|
+
rl.question('', (answer) => resolve(answer));
|
|
91
137
|
return;
|
|
92
138
|
}
|
|
93
139
|
|
|
@@ -106,7 +152,12 @@ function getPassword(prompt) {
|
|
|
106
152
|
process.stdout.write('\n');
|
|
107
153
|
resolve(password);
|
|
108
154
|
} else if (char === '\u0003') {
|
|
109
|
-
|
|
155
|
+
// Ctrl+C: 恢复终端状态后退出
|
|
156
|
+
process.stdin.setRawMode(false);
|
|
157
|
+
process.stdin.removeListener('data', onData);
|
|
158
|
+
process.stdin.pause();
|
|
159
|
+
process.stdout.write('\n');
|
|
160
|
+
process.exit(130);
|
|
110
161
|
} else if (char === '\u007f' || char === '\b') {
|
|
111
162
|
if (password.length > 0) {
|
|
112
163
|
password = password.slice(0, -1);
|
|
@@ -128,19 +179,24 @@ function loadAuthConfig() {
|
|
|
128
179
|
try {
|
|
129
180
|
const configPath = path.join(getConfigDir(), 'auth-config.json');
|
|
130
181
|
const configData = fs.readFileSync(configPath, 'utf8');
|
|
131
|
-
|
|
182
|
+
const config = JSON.parse(configData);
|
|
183
|
+
|
|
184
|
+
if (!Array.isArray(config.users)) {
|
|
185
|
+
config.users = [];
|
|
186
|
+
}
|
|
187
|
+
config.passwordPolicy = { ...DEFAULT_PASSWORD_POLICY, ...(config.passwordPolicy || {}) };
|
|
188
|
+
config.sessionConfig = config.sessionConfig || {
|
|
189
|
+
sessionTimeout: 24,
|
|
190
|
+
maxFailedAttempts: 5,
|
|
191
|
+
lockoutDuration: 30
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return config;
|
|
132
195
|
} catch (error) {
|
|
133
196
|
// 如果文件不存在,返回默认配置
|
|
134
197
|
return {
|
|
135
198
|
users: [],
|
|
136
|
-
passwordPolicy: {
|
|
137
|
-
minLength: 8,
|
|
138
|
-
requireUppercase: true,
|
|
139
|
-
requireLowercase: true,
|
|
140
|
-
requireNumbers: true,
|
|
141
|
-
requireSpecialChars: false,
|
|
142
|
-
passwordExpireDays: 90
|
|
143
|
-
},
|
|
199
|
+
passwordPolicy: { ...DEFAULT_PASSWORD_POLICY },
|
|
144
200
|
sessionConfig: {
|
|
145
201
|
sessionTimeout: 24,
|
|
146
202
|
maxFailedAttempts: 5,
|
|
@@ -150,15 +206,26 @@ function loadAuthConfig() {
|
|
|
150
206
|
}
|
|
151
207
|
}
|
|
152
208
|
|
|
153
|
-
//
|
|
209
|
+
// 保存认证配置(原子写入,且仅当前用户可读写)
|
|
154
210
|
function saveAuthConfig(config) {
|
|
155
211
|
const configDir = getConfigDir();
|
|
156
212
|
// 确保目录存在
|
|
157
213
|
if (!fs.existsSync(configDir)) {
|
|
158
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
214
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
159
215
|
}
|
|
216
|
+
|
|
160
217
|
const configPath = path.join(configDir, 'auth-config.json');
|
|
161
|
-
|
|
218
|
+
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
219
|
+
|
|
220
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
221
|
+
fs.renameSync(tmpPath, configPath);
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
fs.chmodSync(configPath, 0o600);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Windows 等平台可能不支持 chmod,忽略
|
|
227
|
+
}
|
|
228
|
+
|
|
162
229
|
console.log(`配置已保存到: ${configPath}`);
|
|
163
230
|
}
|
|
164
231
|
|
|
@@ -171,41 +238,48 @@ async function setupPassword() {
|
|
|
171
238
|
const authConfig = loadAuthConfig();
|
|
172
239
|
|
|
173
240
|
// 获取用户名
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
241
|
+
const usernameInput = await readInputLine('请输入用户名 (默认: admin): ');
|
|
242
|
+
const username = (usernameInput || '').trim() || 'admin';
|
|
243
|
+
|
|
180
244
|
let password = '';
|
|
181
245
|
let isValidPassword = false;
|
|
182
|
-
|
|
246
|
+
|
|
183
247
|
// 获取并验证密码
|
|
184
248
|
while (!isValidPassword) {
|
|
185
249
|
password = await getPassword('请输入新密码: ');
|
|
186
|
-
|
|
250
|
+
|
|
251
|
+
if (password === null) {
|
|
252
|
+
console.log('\n输入已结束,密码设置已取消');
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
187
256
|
if (password.length === 0) {
|
|
188
257
|
console.log('密码不能为空,请重新输入');
|
|
189
258
|
continue;
|
|
190
259
|
}
|
|
191
|
-
|
|
192
|
-
const validation = validatePassword(password);
|
|
193
|
-
|
|
260
|
+
|
|
261
|
+
const validation = validatePassword(password, authConfig.passwordPolicy);
|
|
262
|
+
|
|
194
263
|
if (!validation.isValid) {
|
|
195
264
|
console.log('\n密码不符合要求:');
|
|
196
265
|
validation.errors.forEach(error => console.log(` - ${error}`));
|
|
197
266
|
console.log('');
|
|
198
267
|
continue;
|
|
199
268
|
}
|
|
200
|
-
|
|
269
|
+
|
|
201
270
|
// 确认密码
|
|
202
271
|
const confirmPassword = await getPassword('请再次输入密码确认: ');
|
|
203
|
-
|
|
272
|
+
|
|
273
|
+
if (confirmPassword === null) {
|
|
274
|
+
console.log('\n输入已结束,密码设置已取消');
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
204
278
|
if (password !== confirmPassword) {
|
|
205
279
|
console.log('\n两次输入的密码不一致,请重新输入\n');
|
|
206
280
|
continue;
|
|
207
281
|
}
|
|
208
|
-
|
|
282
|
+
|
|
209
283
|
isValidPassword = true;
|
|
210
284
|
}
|
|
211
285
|
|
|
@@ -229,6 +303,8 @@ async function setupPassword() {
|
|
|
229
303
|
// 更新现有用户
|
|
230
304
|
authConfig.users[userIndex].passwordHash = passwordHash;
|
|
231
305
|
authConfig.users[userIndex].lastPasswordChange = new Date().toISOString();
|
|
306
|
+
// 重置密码时同时启用账户,避免因账户被禁用而无法登录
|
|
307
|
+
authConfig.users[userIndex].enabled = true;
|
|
232
308
|
console.log(`\n✓ 已更新用户密码: ${username}`);
|
|
233
309
|
}
|
|
234
310
|
|
|
@@ -236,12 +312,16 @@ async function setupPassword() {
|
|
|
236
312
|
saveAuthConfig(authConfig);
|
|
237
313
|
|
|
238
314
|
console.log('✓ 密码设置完成');
|
|
315
|
+
const policy = authConfig.passwordPolicy;
|
|
239
316
|
console.log('\n密码策略要求:');
|
|
240
|
-
console.log(
|
|
241
|
-
console.log(' - 包含大写字母');
|
|
242
|
-
console.log(' - 包含小写字母');
|
|
243
|
-
console.log(' - 包含数字');
|
|
244
|
-
console.log(
|
|
317
|
+
console.log(` - 最少${policy.minLength}个字符`);
|
|
318
|
+
if (policy.requireUppercase) console.log(' - 包含大写字母');
|
|
319
|
+
if (policy.requireLowercase) console.log(' - 包含小写字母');
|
|
320
|
+
if (policy.requireNumbers) console.log(' - 包含数字');
|
|
321
|
+
if (policy.requireSpecialChars) console.log(' - 包含特殊字符');
|
|
322
|
+
if (policy.passwordExpireDays > 0) {
|
|
323
|
+
console.log(` - 密码有效期: ${policy.passwordExpireDays}天`);
|
|
324
|
+
}
|
|
245
325
|
|
|
246
326
|
} catch (error) {
|
|
247
327
|
console.error('设置密码时发生错误:', error.message);
|