i18ntk 2.4.0 → 2.5.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/README.md +6 -5
- package/main/manage/commands/FixerCommand.js +97 -97
- package/main/manage/managers/DebugMenu.js +10 -9
- package/package.json +61 -32
- package/runtime/index.js +14 -8
- package/utils/admin-auth.js +594 -576
- package/utils/config-manager.js +72 -72
- package/utils/env-manager.js +117 -26
- package/utils/i18n-helper.js +50 -49
- package/utils/json-output.js +13 -12
- package/utils/logger.js +7 -6
- package/utils/prompt-helper.js +44 -41
- package/utils/secure-errors.js +156 -154
- package/utils/security.js +235 -233
- package/utils/setup-enforcer.js +110 -109
- package/utils/terminal-icons.js +164 -163
- package/settings/i18ntk-config.json +0 -283
- package/utils/admin-pin.js +0 -520
- package/utils/arg-parser.js +0 -40
- package/utils/cli-args.js +0 -210
- package/utils/mini-commander.js +0 -179
- package/utils/missing-key-validator.js +0 -858
- package/utils/path-utils.js +0 -33
- package/utils/performance-optimizer.js +0 -246
- package/utils/prompt-new.js +0 -55
- package/utils/promptPin.js +0 -76
- package/utils/safe-json.js +0 -40
- package/utils/secure-backup.js +0 -340
- package/utils/security-check-improved.js +0 -393
- package/utils/security-config.js +0 -239
- package/utils/setup-validator.js +0 -717
- package/utils/ultra-performance-optimizer.js +0 -352
package/utils/admin-pin.js
DELETED
|
@@ -1,520 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Admin PIN Management System
|
|
3
|
-
* Handles secure PIN creation, validation, and storage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const crypto = require('crypto');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { getGlobalReadline, ask } = require('./cli');
|
|
10
|
-
const { promptPin: rawPromptPin, promptPinConfirm } = require('./promptPin');
|
|
11
|
-
|
|
12
|
-
// Lazy load i18n to prevent initialization race conditions
|
|
13
|
-
let i18n;
|
|
14
|
-
function getI18n() {
|
|
15
|
-
if (!i18n) {
|
|
16
|
-
try {
|
|
17
|
-
i18n = require('./i18n-helper');
|
|
18
|
-
} catch (error) {
|
|
19
|
-
// Fallback to simple identity function if i18n fails to load
|
|
20
|
-
console.warn('i18n-helper not available, using fallback messages');
|
|
21
|
-
return { t: (key, params = {}) => key };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return i18n;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Use environment variables for configuration
|
|
28
|
-
const SALT_LENGTH = 32;
|
|
29
|
-
const KEY_LENGTH = 32;
|
|
30
|
-
const MEMORY_COST = 2 ** 16; // 64MB
|
|
31
|
-
const TIME_COST = 3;
|
|
32
|
-
const PARALLELISM = 1;
|
|
33
|
-
const ALGORITHM = 'argon2id';
|
|
34
|
-
|
|
35
|
-
class AdminPinManager {
|
|
36
|
-
constructor() {
|
|
37
|
-
this.pinFile = path.join(__dirname, '..', 'settings', 'admin-pin.json');
|
|
38
|
-
this.algorithm = 'aes-256-gcm';
|
|
39
|
-
this.keyLength = 32;
|
|
40
|
-
this.ivLength = 16;
|
|
41
|
-
this.tagLength = 16;
|
|
42
|
-
|
|
43
|
-
// Session management
|
|
44
|
-
this.isAuthenticated = false;
|
|
45
|
-
this.sessionTimeout = 30 * 60 * 1000; // 30 minutes in milliseconds
|
|
46
|
-
this.sessionTimer = null;
|
|
47
|
-
this.lastActivity = null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Generate a random key for encryption (AES-256-GCM)
|
|
52
|
-
* Returns a 32-byte (256-bit) key for AES-256-GCM
|
|
53
|
-
*/
|
|
54
|
-
generateKey() {
|
|
55
|
-
return crypto.randomBytes(32);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Generate secure random IV for AES-256-GCM
|
|
60
|
-
* GCM requires 96-bit (12-byte) IV for optimal security
|
|
61
|
-
*/
|
|
62
|
-
generateIV() {
|
|
63
|
-
return crypto.randomBytes(12);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Encrypt the PIN using AES-256-GCM with proper authentication
|
|
68
|
-
*/
|
|
69
|
-
encryptPin(pin, key) {
|
|
70
|
-
const iv = this.generateIV();
|
|
71
|
-
const cipher = crypto.createCipherGCM('aes-256-gcm', key);
|
|
72
|
-
cipher.setAAD(Buffer.from('admin-pin-v1')); // Additional authenticated data
|
|
73
|
-
|
|
74
|
-
let encrypted = cipher.update(pin, 'utf8', 'hex');
|
|
75
|
-
encrypted += cipher.final('hex');
|
|
76
|
-
|
|
77
|
-
const authTag = cipher.getAuthTag();
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
encrypted,
|
|
81
|
-
iv: iv.toString('hex'),
|
|
82
|
-
authTag: authTag.toString('hex')
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Decrypt the PIN using AES-256-GCM with authentication verification
|
|
88
|
-
*/
|
|
89
|
-
decryptPin(encryptedData, key) {
|
|
90
|
-
try {
|
|
91
|
-
const iv = Buffer.from(encryptedData.iv, 'hex');
|
|
92
|
-
const authTag = Buffer.from(encryptedData.authTag || encryptedData.tag, 'hex');
|
|
93
|
-
|
|
94
|
-
const decipher = crypto.createDecipherGCM('aes-256-gcm', key);
|
|
95
|
-
decipher.setAuthTag(authTag);
|
|
96
|
-
decipher.setAAD(Buffer.from('admin-pin-v1'));
|
|
97
|
-
|
|
98
|
-
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
|
|
99
|
-
decrypted += decipher.final('utf8');
|
|
100
|
-
|
|
101
|
-
return decrypted;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Hash PIN using secure password hashing
|
|
109
|
-
* Uses crypto.scrypt as a secure alternative to argon2
|
|
110
|
-
*/
|
|
111
|
-
async hashPin(pin, salt = null) {
|
|
112
|
-
if (!salt) {
|
|
113
|
-
salt = crypto.randomBytes(32);
|
|
114
|
-
} else if (typeof salt === 'string') {
|
|
115
|
-
salt = Buffer.from(salt, 'hex');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
// Use scrypt (Node.js built-in, more secure than pbkdf2)
|
|
120
|
-
const hash = crypto.scryptSync(pin, salt, 32, {
|
|
121
|
-
N: 16384, // CPU/memory cost parameter
|
|
122
|
-
r: 8, // block size parameter
|
|
123
|
-
p: 1 // parallelization parameter
|
|
124
|
-
});
|
|
125
|
-
return {
|
|
126
|
-
hash: hash.toString('hex'),
|
|
127
|
-
salt: salt.toString('hex'),
|
|
128
|
-
algorithm: 'scrypt'
|
|
129
|
-
};
|
|
130
|
-
} catch (error) {
|
|
131
|
-
// Fallback to pbkdf2
|
|
132
|
-
const hash = crypto.pbkdf2Sync(pin, salt, 100000, 32, 'sha256');
|
|
133
|
-
return {
|
|
134
|
-
hash: hash.toString('hex'),
|
|
135
|
-
salt: salt.toString('hex'),
|
|
136
|
-
algorithm: 'pbkdf2'
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if PIN is weak/common
|
|
143
|
-
*/
|
|
144
|
-
isWeakPin(pin) {
|
|
145
|
-
const weakPins = [
|
|
146
|
-
'1234', '0000', '1111', '2222', '3333', '4444', '5555',
|
|
147
|
-
'6666', '7777', '8888', '9999', '123456', '654321',
|
|
148
|
-
'000000', '111111', '121212', '112233', '12345',
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
return weakPins.includes(pin) ||
|
|
152
|
-
/^(.)\1+$/.test(pin); // All same characters
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Set up a new admin PIN with security checks
|
|
157
|
-
*/
|
|
158
|
-
async setupPin(externalRl = null) {
|
|
159
|
-
// Use shared readline interface
|
|
160
|
-
const hadGlobal = !!global.activeReadlineInterface;
|
|
161
|
-
const rl = externalRl || getGlobalReadline();
|
|
162
|
-
const shouldCloseRL = !externalRl && !hadGlobal;
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const i18nHelper = getI18n();
|
|
166
|
-
console.log('\n' + i18nHelper.t('adminPin.setup_title'));
|
|
167
|
-
console.log(i18nHelper.t('adminPin.setup_separator'));
|
|
168
|
-
console.log(i18nHelper.t('adminPin.setup_description'));
|
|
169
|
-
console.log(i18nHelper.t('adminPin.required_for_title'));
|
|
170
|
-
console.log(i18nHelper.t('adminPin.required_for_1'));
|
|
171
|
-
console.log(i18nHelper.t('adminPin.required_for_2'));
|
|
172
|
-
console.log(i18nHelper.t('adminPin.required_for_3'));
|
|
173
|
-
console.log(i18nHelper.t('adminPin.required_for_4'));
|
|
174
|
-
console.log('\n' + i18nHelper.t('adminPin.setup_note'));
|
|
175
|
-
console.log(i18nHelper.t('adminPin.setup_digits_only'));
|
|
176
|
-
|
|
177
|
-
const pin = await promptPinConfirm(rl, i18nHelper.t('adminPin.enter_new_pin'), i18n.t('adminPin.confirm_pin'));
|
|
178
|
-
|
|
179
|
-
if (!this.validatePin(pin)) {
|
|
180
|
-
console.log(i18nHelper.t('adminPin.invalid_pin_length'));
|
|
181
|
-
console.log(i18nHelper.t('adminPin.invalid_pin_example'));
|
|
182
|
-
if (shouldCloseRL) rl.close();
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (this.isWeakPin(pin)) {
|
|
187
|
-
console.log(i18nHelper.t('adminPin.weak_pin_warning'));
|
|
188
|
-
console.log(i18nHelper.t('adminPin.weak_pin_suggestion'));
|
|
189
|
-
const proceed = await new Promise(resolve => {
|
|
190
|
-
rl.question(i18nHelper.t('adminPin.use_anyway_prompt'), resolve);
|
|
191
|
-
});
|
|
192
|
-
if (proceed.toLowerCase() !== 'yes') {
|
|
193
|
-
if (shouldCloseRL) rl.close();
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Generate encryption key and encrypt PIN
|
|
199
|
-
const key = this.generateKey();
|
|
200
|
-
const encryptedPin = this.encryptPin(pin, key);
|
|
201
|
-
const hashedPin = await this.hashPin(pin);
|
|
202
|
-
|
|
203
|
-
// Store encrypted data
|
|
204
|
-
const pinData = {
|
|
205
|
-
hash: hashedPin.hash,
|
|
206
|
-
salt: hashedPin.salt,
|
|
207
|
-
algorithm: hashedPin.algorithm,
|
|
208
|
-
encrypted: encryptedPin,
|
|
209
|
-
key: key.toString('hex'),
|
|
210
|
-
created: new Date().toISOString(),
|
|
211
|
-
lastChanged: new Date().toISOString(),
|
|
212
|
-
attempts: 0,
|
|
213
|
-
locked: false
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Ensure settings directory exists
|
|
217
|
-
const settingsDir = path.dirname(this.pinFile);
|
|
218
|
-
if (!SecurityUtils.safeExistsSync(settingsDir)) {
|
|
219
|
-
fs.mkdirSync(settingsDir, { recursive: true });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
SecurityUtils.safeWriteFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
223
|
-
|
|
224
|
-
const i18n = getI18n();
|
|
225
|
-
console.log(i18n.t('adminPin.setup_success'));
|
|
226
|
-
console.log(i18n.t('adminPin.setup_warning'));
|
|
227
|
-
|
|
228
|
-
if (shouldCloseRL) rl.close();
|
|
229
|
-
return true;
|
|
230
|
-
|
|
231
|
-
} catch (error) {
|
|
232
|
-
const i18n = getI18n();
|
|
233
|
-
console.error(i18n.t('adminPin.setup_error'), error.message);
|
|
234
|
-
if (shouldCloseRL) rl.close();
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Prompt for PIN with configurable display mode
|
|
241
|
-
*/
|
|
242
|
-
promptPin(rl, message, hideInput = false) {
|
|
243
|
-
return hideInput ? rawPromptPin({ rl, label: message }) : ask(message);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Validate PIN format
|
|
248
|
-
*/
|
|
249
|
-
validatePin(pin) {
|
|
250
|
-
return /^\d{4,6}$/.test(pin);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Check if PIN is set
|
|
255
|
-
*/
|
|
256
|
-
isPinSet() {
|
|
257
|
-
return SecurityUtils.safeExistsSync(this.pinFile);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Start authentication session
|
|
262
|
-
*/
|
|
263
|
-
startSession() {
|
|
264
|
-
this.isAuthenticated = true;
|
|
265
|
-
this.lastActivity = Date.now();
|
|
266
|
-
|
|
267
|
-
// Clear existing timer
|
|
268
|
-
if (this.sessionTimer) {
|
|
269
|
-
clearTimeout(this.sessionTimer);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Set new timeout
|
|
273
|
-
this.sessionTimer = setTimeout(() => {
|
|
274
|
-
this.endSession();
|
|
275
|
-
console.log(i18n.t('adminPin.session_expired'));
|
|
276
|
-
}, this.sessionTimeout);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* End authentication session
|
|
281
|
-
*/
|
|
282
|
-
endSession() {
|
|
283
|
-
this.isAuthenticated = false;
|
|
284
|
-
this.lastActivity = null;
|
|
285
|
-
|
|
286
|
-
if (this.sessionTimer) {
|
|
287
|
-
clearTimeout(this.sessionTimer);
|
|
288
|
-
this.sessionTimer = null;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Check if currently authenticated
|
|
294
|
-
*/
|
|
295
|
-
isCurrentlyAuthenticated() {
|
|
296
|
-
if (!this.isAuthenticated) {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Check if session has expired
|
|
301
|
-
if (this.lastActivity && (Date.now() - this.lastActivity) > this.sessionTimeout) {
|
|
302
|
-
this.endSession();
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Update last activity
|
|
307
|
-
this.lastActivity = Date.now();
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Require authentication (with session support)
|
|
313
|
-
*/
|
|
314
|
-
async requireAuth(forceSetup = false) {
|
|
315
|
-
// Check if already authenticated in current session
|
|
316
|
-
if (this.isCurrentlyAuthenticated()) {
|
|
317
|
-
return true;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Need to authenticate
|
|
321
|
-
const authenticated = await this.verifyPin(forceSetup);
|
|
322
|
-
if (authenticated) {
|
|
323
|
-
this.startSession();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return authenticated;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Constant-time comparison for PIN verification
|
|
331
|
-
* Prevents timing attacks
|
|
332
|
-
*/
|
|
333
|
-
constantTimeCompare(a, b) {
|
|
334
|
-
if (typeof a !== 'string' || typeof b !== 'string') {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
if (a.length !== b.length) {
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
return crypto.timingSafeEqual(Buffer.from(a, 'hex'), Buffer.from(b, 'hex'));
|
|
343
|
-
} catch (error) {
|
|
344
|
-
// Fallback to manual constant-time comparison
|
|
345
|
-
let result = 0;
|
|
346
|
-
for (let i = 0; i < a.length; i++) {
|
|
347
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
348
|
-
}
|
|
349
|
-
return result === 0;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Verify admin PIN with secure hashing and constant-time comparison
|
|
355
|
-
*/
|
|
356
|
-
async verifyPin(forceSetup = false, externalRl = null) {
|
|
357
|
-
if (!this.isPinSet()) {
|
|
358
|
-
if (forceSetup) {
|
|
359
|
-
const i18n = getI18n();
|
|
360
|
-
console.log(i18n.t('adminPin.no_pin_set_setting_up'));
|
|
361
|
-
return await this.setupPin(externalRl);
|
|
362
|
-
} else {
|
|
363
|
-
const i18n = getI18n();
|
|
364
|
-
console.log(i18n.t('adminPin.no_pin_configured_access_denied'));
|
|
365
|
-
console.log(i18n.t('adminPin.use_admin_settings_to_set_pin'));
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Use shared readline interface if available
|
|
371
|
-
const hadGlobal = !!global.activeReadlineInterface;
|
|
372
|
-
const rl = externalRl || getGlobalReadline();
|
|
373
|
-
const shouldCloseRL = !externalRl && !hadGlobal;
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const pinData = JSON.parse(SecurityUtils.safeReadFileSync(this.pinFile, path.dirname(this.pinFile), 'utf8'));
|
|
377
|
-
|
|
378
|
-
if (pinData.locked) {
|
|
379
|
-
const i18n = getI18n();
|
|
380
|
-
console.log(i18n.t('adminPin.locked_out'));
|
|
381
|
-
console.log(i18n.t('adminPin.wait_before_retry'));
|
|
382
|
-
if (shouldCloseRL) rl.close();
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const enteredPin = await this.promptPin(rl, getI18n().t('adminCli.enterPin'), true);
|
|
387
|
-
|
|
388
|
-
// Recompute hash with stored salt
|
|
389
|
-
const salt = Buffer.from(pinData.salt, 'hex');
|
|
390
|
-
let computedHash;
|
|
391
|
-
|
|
392
|
-
if (pinData.algorithm === 'scrypt') {
|
|
393
|
-
computedHash = crypto.scryptSync(enteredPin, salt, 32, {
|
|
394
|
-
N: 16384,
|
|
395
|
-
r: 8,
|
|
396
|
-
p: 1
|
|
397
|
-
});
|
|
398
|
-
} else {
|
|
399
|
-
computedHash = crypto.pbkdf2Sync(enteredPin, salt, 100000, 32, 'sha256');
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const computedHashHex = computedHash.toString('hex');
|
|
403
|
-
|
|
404
|
-
// Use constant-time comparison
|
|
405
|
-
if (this.constantTimeCompare(computedHashHex, pinData.hash)) {
|
|
406
|
-
// Reset attempts on successful login
|
|
407
|
-
pinData.attempts = 0;
|
|
408
|
-
SecurityUtils.safeWriteFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
409
|
-
|
|
410
|
-
const i18n = getI18n();
|
|
411
|
-
console.log(i18n.t('adminPin.access_granted'));
|
|
412
|
-
if (shouldCloseRL) rl.close();
|
|
413
|
-
return true;
|
|
414
|
-
} else {
|
|
415
|
-
pinData.attempts = (pinData.attempts || 0) + 1;
|
|
416
|
-
|
|
417
|
-
if (pinData.attempts >= 3) {
|
|
418
|
-
pinData.locked = true;
|
|
419
|
-
setTimeout(() => {
|
|
420
|
-
pinData.locked = false;
|
|
421
|
-
pinData.attempts = 0;
|
|
422
|
-
SecurityUtils.safeWriteFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
423
|
-
}, 5 * 60 * 1000); // 5 minutes
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
SecurityUtils.safeWriteFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
427
|
-
|
|
428
|
-
const i18n = getI18n();
|
|
429
|
-
console.log(i18n.t('adminPin.incorrect_pin', { attempts: 3 - pinData.attempts }));
|
|
430
|
-
if (shouldCloseRL) rl.close();
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
} catch (error) {
|
|
435
|
-
const i18n = getI18n();
|
|
436
|
-
console.error(i18n.t('adminPin.verify_pin_error'), error.message);
|
|
437
|
-
if (shouldCloseRL) rl.close();
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Prompt user for optional PIN setup
|
|
444
|
-
*/
|
|
445
|
-
async promptOptionalSetup() {
|
|
446
|
-
if (this.isPinSet()) {
|
|
447
|
-
console.log(i18n.t('adminPin.already_configured'));
|
|
448
|
-
return true;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Use shared readline interface if available
|
|
452
|
-
const hadGlobal = !!global.activeReadlineInterface;
|
|
453
|
-
const rl = getGlobalReadline();
|
|
454
|
-
const shouldCloseRL = !hadGlobal;
|
|
455
|
-
|
|
456
|
-
try {
|
|
457
|
-
const i18n = getI18n();
|
|
458
|
-
console.log(i18n.t('adminPin.optional_setup_title'));
|
|
459
|
-
console.log(i18n.t('adminPin.setup_separator'));
|
|
460
|
-
console.log(i18n.t('adminPin.optional_setup_description'));
|
|
461
|
-
console.log(i18n.t('adminPin.required_for_1'));
|
|
462
|
-
console.log(i18n.t('adminPin.required_for_2'));
|
|
463
|
-
console.log(i18n.t('adminPin.required_for_3'));
|
|
464
|
-
console.log(i18n.t('adminPin.required_for_4'));
|
|
465
|
-
console.log('');
|
|
466
|
-
|
|
467
|
-
const response = await new Promise(resolve => {
|
|
468
|
-
rl.question(getI18n().t('adminPin.setup_prompt'), resolve);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
if (shouldCloseRL) rl.close();
|
|
472
|
-
|
|
473
|
-
if (response.toLowerCase() === 'y' || response.toLowerCase() === 'yes') {
|
|
474
|
-
return await this.setupPin();
|
|
475
|
-
} else {
|
|
476
|
-
console.log(getI18n().t('adminPin.skipping_setup'));
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
} catch (error) {
|
|
480
|
-
if (shouldCloseRL) rl.close();
|
|
481
|
-
console.error(getI18n().t('adminPin.setup_prompt_error'), error.message);
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Get PIN display (masked)
|
|
488
|
-
*/
|
|
489
|
-
getPinDisplay() {
|
|
490
|
-
if (!this.isPinSet()) {
|
|
491
|
-
return i18n.t('adminPin.not_set');
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
const pinData = JSON.parse(SecurityUtils.safeReadFileSync(this.pinFile, path.dirname(this.pinFile), 'utf8'));
|
|
496
|
-
const key = Buffer.from(pinData.key, 'hex');
|
|
497
|
-
const decryptedPin = this.decryptPin(pinData.encrypted, key);
|
|
498
|
-
|
|
499
|
-
if (decryptedPin) {
|
|
500
|
-
return '*'.repeat(decryptedPin.length);
|
|
501
|
-
}
|
|
502
|
-
} catch (error) {
|
|
503
|
-
// Ignore errors, return default
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
return i18n.t('adminPin.pin_display_mask');
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Reset PIN
|
|
511
|
-
*/
|
|
512
|
-
async resetPin() {
|
|
513
|
-
if (SecurityUtils.safeExistsSync(this.pinFile)) {
|
|
514
|
-
fs.unlinkSync(this.pinFile);
|
|
515
|
-
}
|
|
516
|
-
return await this.setupPin();
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
module.exports = AdminPinManager;
|
package/utils/arg-parser.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
class ArgumentParser {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.args = {
|
|
4
|
-
_: []
|
|
5
|
-
};
|
|
6
|
-
this.parse(process.argv.slice(2));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
parse(argv) {
|
|
10
|
-
let currentOption = null;
|
|
11
|
-
|
|
12
|
-
for (const arg of argv) {
|
|
13
|
-
if (arg.startsWith('--')) {
|
|
14
|
-
// Handle --option=value or --option value
|
|
15
|
-
if (arg.includes('=')) {
|
|
16
|
-
const [key, value] = arg.split('=');
|
|
17
|
-
this.args[key.slice(2)] = value;
|
|
18
|
-
} else {
|
|
19
|
-
currentOption = arg.slice(2);
|
|
20
|
-
this.args[currentOption] = true;
|
|
21
|
-
}
|
|
22
|
-
} else if (arg.startsWith('-')) {
|
|
23
|
-
// Handle short options
|
|
24
|
-
currentOption = arg.slice(1);
|
|
25
|
-
this.args[currentOption] = true;
|
|
26
|
-
} else if (currentOption) {
|
|
27
|
-
// Handle option value
|
|
28
|
-
this.args[currentOption] = arg;
|
|
29
|
-
currentOption = null;
|
|
30
|
-
} else {
|
|
31
|
-
// Handle positional arguments
|
|
32
|
-
this.args._.push(arg);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return this.args;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
module.exports = ArgumentParser;
|