i18ntk 1.0.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/CHANGELOG.md +401 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dev/README.md +37 -0
- package/dev/debug/README.md +30 -0
- package/dev/debug/complete-console-translations.js +295 -0
- package/dev/debug/console-key-checker.js +408 -0
- package/dev/debug/console-translations.js +335 -0
- package/dev/debug/debugger.js +408 -0
- package/dev/debug/export-missing-keys.js +432 -0
- package/dev/debug/final-normalize.js +236 -0
- package/dev/debug/find-extra-keys.js +68 -0
- package/dev/debug/normalize-locales.js +153 -0
- package/dev/debug/refactor-locales.js +240 -0
- package/dev/debug/reorder-locales.js +85 -0
- package/dev/debug/replace-hardcoded-console.js +378 -0
- package/docs/INSTALLATION.md +449 -0
- package/docs/README.md +222 -0
- package/docs/TODO_ROADMAP.md +279 -0
- package/docs/api/API_REFERENCE.md +377 -0
- package/docs/api/COMPONENTS.md +492 -0
- package/docs/api/CONFIGURATION.md +651 -0
- package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
- package/docs/debug/DEBUG_README.md +30 -0
- package/docs/debug/DEBUG_TOOLS.md +494 -0
- package/docs/development/AGENTS.md +351 -0
- package/docs/development/DEVELOPMENT_RULES.md +165 -0
- package/docs/development/DEV_README.md +37 -0
- package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
- package/docs/reports/ANALYSIS_README.md +17 -0
- package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
- package/docs/reports/SIZING_README.md +18 -0
- package/docs/reports/SUMMARY_README.md +18 -0
- package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
- package/docs/reports/USAGE_README.md +18 -0
- package/docs/reports/VALIDATION_README.md +18 -0
- package/locales/de/auth.json +3 -0
- package/locales/de/common.json +16 -0
- package/locales/de/pagination.json +6 -0
- package/locales/en/auth.json +3 -0
- package/locales/en/common.json +16 -0
- package/locales/en/pagination.json +6 -0
- package/locales/es/auth.json +3 -0
- package/locales/es/common.json +16 -0
- package/locales/es/pagination.json +6 -0
- package/locales/fr/auth.json +3 -0
- package/locales/fr/common.json +16 -0
- package/locales/fr/pagination.json +6 -0
- package/locales/ru/auth.json +3 -0
- package/locales/ru/common.json +16 -0
- package/locales/ru/pagination.json +6 -0
- package/main/i18ntk-analyze.js +625 -0
- package/main/i18ntk-autorun.js +461 -0
- package/main/i18ntk-complete.js +494 -0
- package/main/i18ntk-init.js +686 -0
- package/main/i18ntk-manage.js +848 -0
- package/main/i18ntk-sizing.js +557 -0
- package/main/i18ntk-summary.js +671 -0
- package/main/i18ntk-usage.js +1282 -0
- package/main/i18ntk-validate.js +762 -0
- package/main/ui-i18n.js +332 -0
- package/package.json +152 -0
- package/scripts/fix-missing-translation-keys.js +214 -0
- package/scripts/verify-package.js +168 -0
- package/ui-locales/de.json +637 -0
- package/ui-locales/en.json +688 -0
- package/ui-locales/es.json +637 -0
- package/ui-locales/fr.json +637 -0
- package/ui-locales/ja.json +637 -0
- package/ui-locales/ru.json +637 -0
- package/ui-locales/zh.json +637 -0
- package/utils/admin-auth.js +317 -0
- package/utils/admin-cli.js +353 -0
- package/utils/admin-pin.js +409 -0
- package/utils/detect-language-mismatches.js +454 -0
- package/utils/i18n-helper.js +128 -0
- package/utils/maintain-language-purity.js +433 -0
- package/utils/native-translations.js +478 -0
- package/utils/security.js +384 -0
- package/utils/test-complete-system.js +356 -0
- package/utils/test-console-i18n.js +402 -0
- package/utils/translate-mismatches.js +571 -0
- package/utils/validate-language-purity.js +531 -0
|
@@ -0,0 +1,409 @@
|
|
|
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 readline = require('readline');
|
|
10
|
+
|
|
11
|
+
class AdminPinManager {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.pinFile = path.join(__dirname, '..', 'settings', 'admin-pin.json');
|
|
14
|
+
this.algorithm = 'aes-256-gcm';
|
|
15
|
+
this.keyLength = 32;
|
|
16
|
+
this.ivLength = 16;
|
|
17
|
+
this.tagLength = 16;
|
|
18
|
+
|
|
19
|
+
// Session management
|
|
20
|
+
this.isAuthenticated = false;
|
|
21
|
+
this.sessionTimeout = 30 * 60 * 1000; // 30 minutes in milliseconds
|
|
22
|
+
this.sessionTimer = null;
|
|
23
|
+
this.lastActivity = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a random key for encryption
|
|
28
|
+
*/
|
|
29
|
+
generateKey() {
|
|
30
|
+
return crypto.randomBytes(this.keyLength);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Encrypt the PIN
|
|
35
|
+
*/
|
|
36
|
+
encryptPin(pin, key) {
|
|
37
|
+
const iv = crypto.randomBytes(this.ivLength);
|
|
38
|
+
const cipher = crypto.createCipheriv(this.algorithm, key, iv);
|
|
39
|
+
|
|
40
|
+
let encrypted = cipher.update(pin, 'utf8', 'hex');
|
|
41
|
+
encrypted += cipher.final('hex');
|
|
42
|
+
|
|
43
|
+
const tag = cipher.getAuthTag();
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
encrypted,
|
|
47
|
+
iv: iv.toString('hex'),
|
|
48
|
+
tag: tag.toString('hex')
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Decrypt the PIN
|
|
54
|
+
*/
|
|
55
|
+
decryptPin(encryptedData, key) {
|
|
56
|
+
try {
|
|
57
|
+
const decipher = crypto.createDecipheriv(this.algorithm, key, Buffer.from(encryptedData.iv, 'hex'));
|
|
58
|
+
decipher.setAuthTag(Buffer.from(encryptedData.tag, 'hex'));
|
|
59
|
+
|
|
60
|
+
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
|
|
61
|
+
decrypted += decipher.final('utf8');
|
|
62
|
+
|
|
63
|
+
return decrypted;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hash PIN for verification
|
|
71
|
+
*/
|
|
72
|
+
hashPin(pin) {
|
|
73
|
+
return crypto.createHash('sha256').update(pin).digest('hex');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Set up a new admin PIN
|
|
78
|
+
*/
|
|
79
|
+
async setupPin() {
|
|
80
|
+
const rl = readline.createInterface({
|
|
81
|
+
input: process.stdin,
|
|
82
|
+
output: process.stdout
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
console.log('\nš Admin PIN Setup');
|
|
87
|
+
console.log('============================================================');
|
|
88
|
+
console.log('Create a 4-6 digit PIN for admin access to sensitive settings.');
|
|
89
|
+
console.log('This PIN will be required for:');
|
|
90
|
+
console.log(' ⢠Changing security settings');
|
|
91
|
+
console.log(' ⢠Modifying advanced configurations');
|
|
92
|
+
console.log(' ⢠Accessing debug tools');
|
|
93
|
+
console.log(' ⢠Resetting settings');
|
|
94
|
+
|
|
95
|
+
const pin = await this.promptPin(rl, 'Enter new admin PIN (4-6 digits): ');
|
|
96
|
+
|
|
97
|
+
if (!this.validatePin(pin)) {
|
|
98
|
+
console.log('ā Invalid PIN. Must be 4-6 digits.');
|
|
99
|
+
rl.close();
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const confirmPin = await this.promptPin(rl, 'Confirm admin PIN: ');
|
|
104
|
+
|
|
105
|
+
if (pin !== confirmPin) {
|
|
106
|
+
console.log('ā PINs do not match.');
|
|
107
|
+
rl.close();
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Generate encryption key and encrypt PIN
|
|
112
|
+
const key = this.generateKey();
|
|
113
|
+
const encryptedPin = this.encryptPin(pin, key);
|
|
114
|
+
const hashedPin = this.hashPin(pin);
|
|
115
|
+
|
|
116
|
+
// Store encrypted data
|
|
117
|
+
const pinData = {
|
|
118
|
+
hash: hashedPin,
|
|
119
|
+
encrypted: encryptedPin,
|
|
120
|
+
key: key.toString('hex'),
|
|
121
|
+
created: new Date().toISOString(),
|
|
122
|
+
attempts: 0,
|
|
123
|
+
locked: false
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Ensure settings directory exists
|
|
127
|
+
const settingsDir = path.dirname(this.pinFile);
|
|
128
|
+
if (!fs.existsSync(settingsDir)) {
|
|
129
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fs.writeFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
133
|
+
|
|
134
|
+
console.log('ā
Admin PIN has been set successfully!');
|
|
135
|
+
console.log('ā ļø Keep this PIN secure. It cannot be recovered if lost.');
|
|
136
|
+
|
|
137
|
+
rl.close();
|
|
138
|
+
return true;
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('ā Error setting up PIN:', error.message);
|
|
142
|
+
rl.close();
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Prompt for PIN with hidden input
|
|
149
|
+
*/
|
|
150
|
+
promptPin(rl, message) {
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
process.stdout.write(message);
|
|
153
|
+
|
|
154
|
+
// Hide input
|
|
155
|
+
process.stdin.setRawMode(true);
|
|
156
|
+
let pin = '';
|
|
157
|
+
|
|
158
|
+
const onData = (char) => {
|
|
159
|
+
const charStr = char.toString();
|
|
160
|
+
|
|
161
|
+
if (charStr === '\r' || charStr === '\n') {
|
|
162
|
+
process.stdin.setRawMode(false);
|
|
163
|
+
process.stdin.removeListener('data', onData);
|
|
164
|
+
process.stdout.write('\n');
|
|
165
|
+
resolve(pin);
|
|
166
|
+
} else if (charStr === '\u0008' || charStr === '\u007f') {
|
|
167
|
+
// Backspace
|
|
168
|
+
if (pin.length > 0) {
|
|
169
|
+
pin = pin.slice(0, -1);
|
|
170
|
+
process.stdout.write('\b \b');
|
|
171
|
+
}
|
|
172
|
+
} else if (charStr >= '0' && charStr <= '9') {
|
|
173
|
+
pin += charStr;
|
|
174
|
+
process.stdout.write('*');
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
process.stdin.on('data', onData);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validate PIN format
|
|
184
|
+
*/
|
|
185
|
+
validatePin(pin) {
|
|
186
|
+
return /^\d{4,6}$/.test(pin);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check if PIN is set
|
|
191
|
+
*/
|
|
192
|
+
isPinSet() {
|
|
193
|
+
return fs.existsSync(this.pinFile);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Start authentication session
|
|
198
|
+
*/
|
|
199
|
+
startSession() {
|
|
200
|
+
this.isAuthenticated = true;
|
|
201
|
+
this.lastActivity = Date.now();
|
|
202
|
+
|
|
203
|
+
// Clear existing timer
|
|
204
|
+
if (this.sessionTimer) {
|
|
205
|
+
clearTimeout(this.sessionTimer);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Set new timeout
|
|
209
|
+
this.sessionTimer = setTimeout(() => {
|
|
210
|
+
this.endSession();
|
|
211
|
+
console.log('\nā° Admin session expired due to inactivity.');
|
|
212
|
+
}, this.sessionTimeout);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* End authentication session
|
|
217
|
+
*/
|
|
218
|
+
endSession() {
|
|
219
|
+
this.isAuthenticated = false;
|
|
220
|
+
this.lastActivity = null;
|
|
221
|
+
|
|
222
|
+
if (this.sessionTimer) {
|
|
223
|
+
clearTimeout(this.sessionTimer);
|
|
224
|
+
this.sessionTimer = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if currently authenticated
|
|
230
|
+
*/
|
|
231
|
+
isCurrentlyAuthenticated() {
|
|
232
|
+
if (!this.isAuthenticated) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check if session has expired
|
|
237
|
+
if (this.lastActivity && (Date.now() - this.lastActivity) > this.sessionTimeout) {
|
|
238
|
+
this.endSession();
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Update last activity
|
|
243
|
+
this.lastActivity = Date.now();
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Require authentication (with session support)
|
|
249
|
+
*/
|
|
250
|
+
async requireAuth(forceSetup = false) {
|
|
251
|
+
// Check if already authenticated in current session
|
|
252
|
+
if (this.isCurrentlyAuthenticated()) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Need to authenticate
|
|
257
|
+
const authenticated = await this.verifyPin(forceSetup);
|
|
258
|
+
if (authenticated) {
|
|
259
|
+
this.startSession();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return authenticated;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Verify admin PIN
|
|
267
|
+
*/
|
|
268
|
+
async verifyPin(forceSetup = false) {
|
|
269
|
+
if (!this.isPinSet()) {
|
|
270
|
+
if (forceSetup) {
|
|
271
|
+
console.log('ā ļø No admin PIN set. Setting up PIN...');
|
|
272
|
+
return await this.setupPin();
|
|
273
|
+
} else {
|
|
274
|
+
console.log('ā ļø No admin PIN configured. Access denied.');
|
|
275
|
+
console.log('š” Use the admin settings to set up a PIN first.');
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const rl = readline.createInterface({
|
|
281
|
+
input: process.stdin,
|
|
282
|
+
output: process.stdout
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const pinData = JSON.parse(fs.readFileSync(this.pinFile, 'utf8'));
|
|
287
|
+
|
|
288
|
+
if (pinData.locked) {
|
|
289
|
+
console.log('š Admin access is locked due to too many failed attempts.');
|
|
290
|
+
console.log('Please wait 5 minutes before trying again.');
|
|
291
|
+
rl.close();
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const enteredPin = await this.promptPin(rl, 'š Enter admin PIN: ');
|
|
296
|
+
const hashedEnteredPin = this.hashPin(enteredPin);
|
|
297
|
+
|
|
298
|
+
if (hashedEnteredPin === pinData.hash) {
|
|
299
|
+
// Reset attempts on successful login
|
|
300
|
+
pinData.attempts = 0;
|
|
301
|
+
fs.writeFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
302
|
+
|
|
303
|
+
console.log('ā
Admin access granted.');
|
|
304
|
+
rl.close();
|
|
305
|
+
return true;
|
|
306
|
+
} else {
|
|
307
|
+
pinData.attempts = (pinData.attempts || 0) + 1;
|
|
308
|
+
|
|
309
|
+
if (pinData.attempts >= 3) {
|
|
310
|
+
pinData.locked = true;
|
|
311
|
+
setTimeout(() => {
|
|
312
|
+
pinData.locked = false;
|
|
313
|
+
pinData.attempts = 0;
|
|
314
|
+
fs.writeFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
315
|
+
}, 5 * 60 * 1000); // 5 minutes
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fs.writeFileSync(this.pinFile, JSON.stringify(pinData, null, 2));
|
|
319
|
+
|
|
320
|
+
console.log(`ā Incorrect PIN. ${3 - pinData.attempts} attempts remaining.`);
|
|
321
|
+
rl.close();
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error('ā Error verifying PIN:', error.message);
|
|
327
|
+
rl.close();
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Prompt user for optional PIN setup
|
|
334
|
+
*/
|
|
335
|
+
async promptOptionalSetup() {
|
|
336
|
+
if (this.isPinSet()) {
|
|
337
|
+
console.log('ā
Admin PIN is already configured.');
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const rl = readline.createInterface({
|
|
342
|
+
input: process.stdin,
|
|
343
|
+
output: process.stdout
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
console.log('\nš Admin PIN Setup (Optional)');
|
|
348
|
+
console.log('============================================================');
|
|
349
|
+
console.log('Admin PIN protection adds security for sensitive operations like:');
|
|
350
|
+
console.log(' ⢠Changing security settings');
|
|
351
|
+
console.log(' ⢠Modifying advanced configurations');
|
|
352
|
+
console.log(' ⢠Accessing debug tools');
|
|
353
|
+
console.log(' ⢠Resetting settings');
|
|
354
|
+
console.log('');
|
|
355
|
+
|
|
356
|
+
const response = await new Promise(resolve => {
|
|
357
|
+
rl.question('Would you like to set up an admin PIN? (y/N): ', resolve);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
rl.close();
|
|
361
|
+
|
|
362
|
+
if (response.toLowerCase() === 'y' || response.toLowerCase() === 'yes') {
|
|
363
|
+
return await this.setupPin();
|
|
364
|
+
} else {
|
|
365
|
+
console.log('āļø Skipping admin PIN setup. You can set it up later in settings.');
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
} catch (error) {
|
|
369
|
+
rl.close();
|
|
370
|
+
console.error('ā Error during PIN setup prompt:', error.message);
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get PIN display (masked)
|
|
377
|
+
*/
|
|
378
|
+
getPinDisplay() {
|
|
379
|
+
if (!this.isPinSet()) {
|
|
380
|
+
return 'Not Set';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const pinData = JSON.parse(fs.readFileSync(this.pinFile, 'utf8'));
|
|
385
|
+
const key = Buffer.from(pinData.key, 'hex');
|
|
386
|
+
const decryptedPin = this.decryptPin(pinData.encrypted, key);
|
|
387
|
+
|
|
388
|
+
if (decryptedPin) {
|
|
389
|
+
return '*'.repeat(decryptedPin.length);
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
// Ignore errors, return default
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return '####';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Reset PIN
|
|
400
|
+
*/
|
|
401
|
+
async resetPin() {
|
|
402
|
+
if (fs.existsSync(this.pinFile)) {
|
|
403
|
+
fs.unlinkSync(this.pinFile);
|
|
404
|
+
}
|
|
405
|
+
return await this.setupPin();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
module.exports = AdminPinManager;
|