@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/setup-password.js CHANGED
@@ -20,32 +20,80 @@ const rl = readline.createInterface({
20
20
  output: process.stdout
21
21
  });
22
22
 
23
- // 密码复杂度验证
24
- function validatePassword(password) {
25
- const minLength = 8;
26
- const hasUppercase = /[A-Z]/.test(password);
27
- const hasLowercase = /[a-z]/.test(password);
28
- const hasNumbers = /\d/.test(password);
29
- const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
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 (!hasUppercase) {
80
+
81
+ if (rules.requireUppercase && !/[A-Z]/.test(password)) {
38
82
  errors.push('密码必须包含至少一个大写字母');
39
83
  }
40
-
41
- if (!hasLowercase) {
84
+
85
+ if (rules.requireLowercase && !/[a-z]/.test(password)) {
42
86
  errors.push('密码必须包含至少一个小写字母');
43
87
  }
44
-
45
- if (!hasNumbers) {
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
- const rl2 = readline.createInterface({
59
- input: process.stdin,
60
- output: process.stdout
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
- rl2.question('', (answer) => {
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
- process.exit();
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
- return JSON.parse(configData);
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
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
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 username = await new Promise((resolve) => {
175
- rl.question('请输入用户名 (默认: admin): ', (input) => {
176
- resolve(input.trim() || 'admin');
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(' - 最少8个字符');
241
- console.log(' - 包含大写字母');
242
- console.log(' - 包含小写字母');
243
- console.log(' - 包含数字');
244
- console.log(` - 密码有效期: ${authConfig.passwordPolicy.passwordExpireDays}天`);
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);