@chenpu17/tcpmapping 2.1.0 → 2.4.0

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
+ }
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
+ }
30
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,36 +103,73 @@ function validatePassword(password) {
55
103
  // 获取用户输入(隐藏密码)
56
104
  function getPassword(prompt) {
57
105
  return new Promise((resolve) => {
58
- // 对于密码输入,我们使用标准输入但隐藏输出
106
+ // 非 TTY 环境(如管道输入)无法使用原始模式,退回到普通输入
107
+ if (!process.stdin.isTTY) {
108
+ readInputLine(prompt).then(resolve);
109
+ return;
110
+ }
111
+
112
+ // Windows 兼容:使用简单的输入方式
113
+ // 在某些终端上密码可能可见,但这是最兼容的方式
114
+ if (process.platform === 'win32') {
115
+ // Windows: 尝试隐藏输入
116
+ const spawn = require('child_process').spawnSync;
117
+ process.stdout.write(prompt);
118
+
119
+ try {
120
+ // 使用 PowerShell 读取密码
121
+ const result = spawn('powershell', [
122
+ '-Command',
123
+ '$pwd = Read-Host -AsSecureString; [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd))'
124
+ ], { stdio: ['inherit', 'pipe', 'inherit'] });
125
+
126
+ if (result.status === 0 && result.stdout) {
127
+ resolve(result.stdout.toString().trim());
128
+ return;
129
+ }
130
+ } catch (e) {
131
+ // PowerShell 不可用,回退到普通输入
132
+ }
133
+
134
+ // 回退方案:普通输入(密码可见)
135
+ console.log('(密码将可见)');
136
+ rl.question('', (answer) => resolve(answer));
137
+ return;
138
+ }
139
+
140
+ // Unix/Mac: 使用 raw mode 隐藏密码
59
141
  process.stdout.write(prompt);
60
142
  process.stdin.setRawMode(true);
61
143
  process.stdin.setEncoding('utf8');
62
-
144
+
63
145
  let password = '';
64
-
146
+
65
147
  const onData = (char) => {
66
148
  if (char === '\n' || char === '\r' || char === '\u0004') {
67
- // 结束输入
68
149
  process.stdin.setRawMode(false);
69
150
  process.stdin.removeListener('data', onData);
151
+ process.stdin.pause();
70
152
  process.stdout.write('\n');
71
153
  resolve(password);
72
154
  } else if (char === '\u0003') {
73
- // Ctrl+C
74
- process.exit();
75
- } else if (char === '\u007f') {
76
- // 退格键
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);
161
+ } else if (char === '\u007f' || char === '\b') {
77
162
  if (password.length > 0) {
78
163
  password = password.slice(0, -1);
79
164
  process.stdout.write('\b \b');
80
165
  }
81
- } else {
82
- // 普通字符
166
+ } else if (char.charCodeAt(0) >= 32) {
83
167
  password += char;
84
168
  process.stdout.write('*');
85
169
  }
86
170
  };
87
-
171
+
172
+ process.stdin.resume();
88
173
  process.stdin.on('data', onData);
89
174
  });
90
175
  }
@@ -94,19 +179,24 @@ function loadAuthConfig() {
94
179
  try {
95
180
  const configPath = path.join(getConfigDir(), 'auth-config.json');
96
181
  const configData = fs.readFileSync(configPath, 'utf8');
97
- 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;
98
195
  } catch (error) {
99
196
  // 如果文件不存在,返回默认配置
100
197
  return {
101
198
  users: [],
102
- passwordPolicy: {
103
- minLength: 8,
104
- requireUppercase: true,
105
- requireLowercase: true,
106
- requireNumbers: true,
107
- requireSpecialChars: false,
108
- passwordExpireDays: 90
109
- },
199
+ passwordPolicy: { ...DEFAULT_PASSWORD_POLICY },
110
200
  sessionConfig: {
111
201
  sessionTimeout: 24,
112
202
  maxFailedAttempts: 5,
@@ -116,15 +206,26 @@ function loadAuthConfig() {
116
206
  }
117
207
  }
118
208
 
119
- // 保存认证配置
209
+ // 保存认证配置(原子写入,且仅当前用户可读写)
120
210
  function saveAuthConfig(config) {
121
211
  const configDir = getConfigDir();
122
212
  // 确保目录存在
123
213
  if (!fs.existsSync(configDir)) {
124
- fs.mkdirSync(configDir, { recursive: true });
214
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
125
215
  }
216
+
126
217
  const configPath = path.join(configDir, 'auth-config.json');
127
- 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
+
128
229
  console.log(`配置已保存到: ${configPath}`);
129
230
  }
130
231
 
@@ -137,41 +238,48 @@ async function setupPassword() {
137
238
  const authConfig = loadAuthConfig();
138
239
 
139
240
  // 获取用户名
140
- const username = await new Promise((resolve) => {
141
- rl.question('请输入用户名 (默认: admin): ', (input) => {
142
- resolve(input.trim() || 'admin');
143
- });
144
- });
145
-
241
+ const usernameInput = await readInputLine('请输入用户名 (默认: admin): ');
242
+ const username = (usernameInput || '').trim() || 'admin';
243
+
146
244
  let password = '';
147
245
  let isValidPassword = false;
148
-
246
+
149
247
  // 获取并验证密码
150
248
  while (!isValidPassword) {
151
249
  password = await getPassword('请输入新密码: ');
152
-
250
+
251
+ if (password === null) {
252
+ console.log('\n输入已结束,密码设置已取消');
253
+ process.exit(1);
254
+ }
255
+
153
256
  if (password.length === 0) {
154
257
  console.log('密码不能为空,请重新输入');
155
258
  continue;
156
259
  }
157
-
158
- const validation = validatePassword(password);
159
-
260
+
261
+ const validation = validatePassword(password, authConfig.passwordPolicy);
262
+
160
263
  if (!validation.isValid) {
161
264
  console.log('\n密码不符合要求:');
162
265
  validation.errors.forEach(error => console.log(` - ${error}`));
163
266
  console.log('');
164
267
  continue;
165
268
  }
166
-
269
+
167
270
  // 确认密码
168
271
  const confirmPassword = await getPassword('请再次输入密码确认: ');
169
-
272
+
273
+ if (confirmPassword === null) {
274
+ console.log('\n输入已结束,密码设置已取消');
275
+ process.exit(1);
276
+ }
277
+
170
278
  if (password !== confirmPassword) {
171
279
  console.log('\n两次输入的密码不一致,请重新输入\n');
172
280
  continue;
173
281
  }
174
-
282
+
175
283
  isValidPassword = true;
176
284
  }
177
285
 
@@ -195,6 +303,8 @@ async function setupPassword() {
195
303
  // 更新现有用户
196
304
  authConfig.users[userIndex].passwordHash = passwordHash;
197
305
  authConfig.users[userIndex].lastPasswordChange = new Date().toISOString();
306
+ // 重置密码时同时启用账户,避免因账户被禁用而无法登录
307
+ authConfig.users[userIndex].enabled = true;
198
308
  console.log(`\n✓ 已更新用户密码: ${username}`);
199
309
  }
200
310
 
@@ -202,12 +312,16 @@ async function setupPassword() {
202
312
  saveAuthConfig(authConfig);
203
313
 
204
314
  console.log('✓ 密码设置完成');
315
+ const policy = authConfig.passwordPolicy;
205
316
  console.log('\n密码策略要求:');
206
- console.log(' - 最少8个字符');
207
- console.log(' - 包含大写字母');
208
- console.log(' - 包含小写字母');
209
- console.log(' - 包含数字');
210
- 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
+ }
211
325
 
212
326
  } catch (error) {
213
327
  console.error('设置密码时发生错误:', error.message);