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,317 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const SecurityUtils = require('./security');
|
|
5
|
+
const SettingsManager = require('../settings/settings-manager');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Admin Authentication Module
|
|
9
|
+
* Provides secure PIN-based authentication for administrative operations
|
|
10
|
+
*/
|
|
11
|
+
class AdminAuth {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.configPath = path.join(process.cwd(), '.i18n-admin-config.json');
|
|
14
|
+
this.settingsManager = SettingsManager;
|
|
15
|
+
|
|
16
|
+
// Get settings from SettingsManager
|
|
17
|
+
const securitySettings = this.settingsManager.getSecurity();
|
|
18
|
+
this.sessionTimeout = (securitySettings.sessionTimeout || 30) * 60 * 1000; // Convert minutes to milliseconds
|
|
19
|
+
this.maxAttempts = securitySettings.maxFailedAttempts || 3;
|
|
20
|
+
this.lockoutDuration = (securitySettings.lockoutDuration || 15) * 60 * 1000; // Convert minutes to milliseconds
|
|
21
|
+
this.keepAuthenticatedUntilExit = securitySettings.keepAuthenticatedUntilExit !== false;
|
|
22
|
+
|
|
23
|
+
this.activeSessions = new Map();
|
|
24
|
+
this.failedAttempts = new Map();
|
|
25
|
+
this.lockouts = new Map();
|
|
26
|
+
|
|
27
|
+
// Clean up expired sessions every 5 minutes
|
|
28
|
+
this.cleanupInterval = setInterval(this.cleanupExpiredSessions.bind(this), 5 * 60 * 1000);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize admin authentication system
|
|
33
|
+
*/
|
|
34
|
+
async initialize() {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(this.configPath)) {
|
|
37
|
+
// Create default config if it doesn't exist
|
|
38
|
+
const defaultConfig = {
|
|
39
|
+
enabled: false,
|
|
40
|
+
pinHash: null,
|
|
41
|
+
salt: null,
|
|
42
|
+
createdAt: new Date().toISOString(),
|
|
43
|
+
lastModified: new Date().toISOString()
|
|
44
|
+
};
|
|
45
|
+
await this.saveConfig(defaultConfig);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SecurityUtils.logSecurityEvent('admin_auth_initialized', 'info', 'Admin authentication system initialized');
|
|
49
|
+
return true;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
SecurityUtils.logSecurityEvent('admin_auth_init_error', 'error', `Failed to initialize admin auth: ${error.message}`);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load admin configuration
|
|
58
|
+
*/
|
|
59
|
+
async loadConfig() {
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.existsSync(this.configPath)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const content = await fs.promises.readFile(this.configPath, 'utf8');
|
|
66
|
+
return SecurityUtils.safeParseJSON(content);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
SecurityUtils.logSecurityEvent('admin_config_load_error', 'error', `Failed to load admin config: ${error.message}`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Save admin configuration
|
|
75
|
+
*/
|
|
76
|
+
async saveConfig(config) {
|
|
77
|
+
try {
|
|
78
|
+
const content = JSON.stringify(config, null, 2);
|
|
79
|
+
await fs.promises.writeFile(this.configPath, content, { mode: 0o600 }); // Restrict permissions
|
|
80
|
+
SecurityUtils.logSecurityEvent('admin_config_saved', 'info', 'Admin configuration saved');
|
|
81
|
+
return true;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
SecurityUtils.logSecurityEvent('admin_config_save_error', 'error', `Failed to save admin config: ${error.message}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set up admin PIN
|
|
90
|
+
*/
|
|
91
|
+
async setupPin(pin) {
|
|
92
|
+
try {
|
|
93
|
+
// Validate PIN format (4 digits)
|
|
94
|
+
if (!/^\d{4}$/.test(pin)) {
|
|
95
|
+
throw new Error('PIN must be exactly 4 digits');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Generate salt and hash
|
|
99
|
+
const salt = crypto.randomBytes(32).toString('hex');
|
|
100
|
+
const pinHash = this.hashPin(pin, salt);
|
|
101
|
+
|
|
102
|
+
const config = {
|
|
103
|
+
enabled: true,
|
|
104
|
+
pinHash,
|
|
105
|
+
salt,
|
|
106
|
+
createdAt: new Date().toISOString(),
|
|
107
|
+
lastModified: new Date().toISOString()
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const success = await this.saveConfig(config);
|
|
111
|
+
if (success) {
|
|
112
|
+
SecurityUtils.logSecurityEvent('admin_pin_setup', 'info', 'Admin PIN configured successfully');
|
|
113
|
+
}
|
|
114
|
+
return success;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
SecurityUtils.logSecurityEvent('admin_pin_setup_error', 'error', `Failed to setup PIN: ${error.message}`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Hash PIN with salt
|
|
123
|
+
*/
|
|
124
|
+
hashPin(pin, salt) {
|
|
125
|
+
return crypto.pbkdf2Sync(pin, salt, 100000, 64, 'sha512').toString('hex');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Verify PIN
|
|
130
|
+
*/
|
|
131
|
+
async verifyPin(pin) {
|
|
132
|
+
try {
|
|
133
|
+
const config = await this.loadConfig();
|
|
134
|
+
if (!config || !config.enabled) {
|
|
135
|
+
return true; // No authentication required if not enabled
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check for lockout
|
|
139
|
+
const clientId = 'local'; // In a real app, this would be client IP or session ID
|
|
140
|
+
if (this.isLockedOut(clientId)) {
|
|
141
|
+
SecurityUtils.logSecurityEvent('admin_auth_lockout', 'warning', 'Authentication attempt during lockout period');
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Validate PIN format
|
|
146
|
+
if (!/^\d{4}$/.test(pin)) {
|
|
147
|
+
this.recordFailedAttempt(clientId);
|
|
148
|
+
SecurityUtils.logSecurityEvent('admin_auth_invalid_format', 'warning', 'Invalid PIN format attempted');
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Verify PIN
|
|
153
|
+
const pinHash = this.hashPin(pin, config.salt);
|
|
154
|
+
const isValid = pinHash === config.pinHash;
|
|
155
|
+
|
|
156
|
+
if (isValid) {
|
|
157
|
+
this.clearFailedAttempts(clientId);
|
|
158
|
+
SecurityUtils.logSecurityEvent('admin_auth_success', 'info', 'Admin authentication successful');
|
|
159
|
+
return true;
|
|
160
|
+
} else {
|
|
161
|
+
this.recordFailedAttempt(clientId);
|
|
162
|
+
SecurityUtils.logSecurityEvent('admin_auth_failure', 'warning', 'Admin authentication failed');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
SecurityUtils.logSecurityEvent('admin_auth_error', 'error', `Authentication error: ${error.message}`);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if authentication is required
|
|
173
|
+
*/
|
|
174
|
+
async isAuthRequired() {
|
|
175
|
+
// Check if admin PIN is enabled in settings
|
|
176
|
+
if (!this.settingsManager.isAdminPinEnabled()) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const config = await this.loadConfig();
|
|
181
|
+
return config && config.enabled;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Disable admin authentication
|
|
186
|
+
*/
|
|
187
|
+
async disableAuth() {
|
|
188
|
+
try {
|
|
189
|
+
const config = await this.loadConfig();
|
|
190
|
+
if (config) {
|
|
191
|
+
config.enabled = false;
|
|
192
|
+
config.lastModified = new Date().toISOString();
|
|
193
|
+
const success = await this.saveConfig(config);
|
|
194
|
+
if (success) {
|
|
195
|
+
SecurityUtils.logSecurityEvent('admin_auth_disabled', 'info', 'Admin authentication disabled');
|
|
196
|
+
}
|
|
197
|
+
return success;
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
SecurityUtils.logSecurityEvent('admin_auth_disable_error', 'error', `Failed to disable auth: ${error.message}`);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Record failed authentication attempt
|
|
208
|
+
*/
|
|
209
|
+
recordFailedAttempt(clientId) {
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
const attempts = this.failedAttempts.get(clientId) || [];
|
|
212
|
+
|
|
213
|
+
// Remove old attempts (older than lockout duration)
|
|
214
|
+
const recentAttempts = attempts.filter(time => now - time < this.lockoutDuration);
|
|
215
|
+
recentAttempts.push(now);
|
|
216
|
+
|
|
217
|
+
this.failedAttempts.set(clientId, recentAttempts);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Clear failed attempts for client
|
|
222
|
+
*/
|
|
223
|
+
clearFailedAttempts(clientId) {
|
|
224
|
+
this.failedAttempts.delete(clientId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if client is locked out
|
|
229
|
+
*/
|
|
230
|
+
isLockedOut(clientId) {
|
|
231
|
+
const attempts = this.failedAttempts.get(clientId) || [];
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
|
|
234
|
+
// Remove old attempts
|
|
235
|
+
const recentAttempts = attempts.filter(time => now - time < this.lockoutDuration);
|
|
236
|
+
this.failedAttempts.set(clientId, recentAttempts);
|
|
237
|
+
|
|
238
|
+
return recentAttempts.length >= this.maxAttempts;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Create authenticated session
|
|
243
|
+
*/
|
|
244
|
+
createSession() {
|
|
245
|
+
const sessionId = crypto.randomBytes(32).toString('hex');
|
|
246
|
+
const expiresAt = Date.now() + this.sessionTimeout;
|
|
247
|
+
|
|
248
|
+
this.activeSessions.set(sessionId, {
|
|
249
|
+
createdAt: Date.now(),
|
|
250
|
+
expiresAt,
|
|
251
|
+
lastActivity: Date.now()
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
SecurityUtils.logSecurityEvent('admin_session_created', 'info', 'Admin session created');
|
|
255
|
+
return sessionId;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Validate session
|
|
260
|
+
*/
|
|
261
|
+
validateSession(sessionId) {
|
|
262
|
+
const session = this.activeSessions.get(sessionId);
|
|
263
|
+
if (!session) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const now = Date.now();
|
|
268
|
+
if (now > session.expiresAt) {
|
|
269
|
+
this.activeSessions.delete(sessionId);
|
|
270
|
+
SecurityUtils.logSecurityEvent('admin_session_expired', 'info', 'Admin session expired');
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Update last activity
|
|
275
|
+
session.lastActivity = now;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Destroy session
|
|
281
|
+
*/
|
|
282
|
+
destroySession(sessionId) {
|
|
283
|
+
const deleted = this.activeSessions.delete(sessionId);
|
|
284
|
+
if (deleted) {
|
|
285
|
+
SecurityUtils.logSecurityEvent('admin_session_destroyed', 'info', 'Admin session destroyed');
|
|
286
|
+
}
|
|
287
|
+
return deleted;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Clean up expired sessions
|
|
292
|
+
*/
|
|
293
|
+
cleanupExpiredSessions() {
|
|
294
|
+
const now = Date.now();
|
|
295
|
+
let cleaned = 0;
|
|
296
|
+
|
|
297
|
+
for (const [sessionId, session] of this.activeSessions.entries()) {
|
|
298
|
+
if (now > session.expiresAt) {
|
|
299
|
+
this.activeSessions.delete(sessionId);
|
|
300
|
+
cleaned++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (cleaned > 0) {
|
|
305
|
+
SecurityUtils.logSecurityEvent('admin_sessions_cleaned', 'info', `Cleaned up ${cleaned} expired sessions`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Clean up expired sessions (alias for backward compatibility)
|
|
311
|
+
*/
|
|
312
|
+
cleanupSessions() {
|
|
313
|
+
return this.cleanupExpiredSessions();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module.exports = AdminAuth;
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const AdminAuth = require('./admin-auth');
|
|
3
|
+
const SecurityUtils = require('./security');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CLI Helper for Admin Authentication
|
|
7
|
+
*/
|
|
8
|
+
class AdminCLI {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.adminAuth = new AdminAuth();
|
|
11
|
+
this.rl = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize readline interface
|
|
16
|
+
*/
|
|
17
|
+
initReadline() {
|
|
18
|
+
if (!this.rl) {
|
|
19
|
+
this.rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
terminal: true,
|
|
23
|
+
historySize: 0
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return this.rl;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Close readline interface
|
|
31
|
+
*/
|
|
32
|
+
closeReadline() {
|
|
33
|
+
if (this.rl) {
|
|
34
|
+
this.rl.close();
|
|
35
|
+
this.rl = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Prompt for PIN input (hidden)
|
|
41
|
+
*/
|
|
42
|
+
async promptPin(message = 'Enter admin PIN: ') {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const rl = this.initReadline();
|
|
45
|
+
|
|
46
|
+
// Check if stdin is a TTY (interactive terminal)
|
|
47
|
+
const stdin = process.stdin;
|
|
48
|
+
const isTTY = stdin.isTTY;
|
|
49
|
+
|
|
50
|
+
if (isTTY) {
|
|
51
|
+
// Hide input for PIN in interactive mode
|
|
52
|
+
stdin.setRawMode(true);
|
|
53
|
+
stdin.resume();
|
|
54
|
+
stdin.setEncoding('utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isTTY) {
|
|
58
|
+
// Interactive mode with hidden input
|
|
59
|
+
let pin = '';
|
|
60
|
+
process.stdout.write(message);
|
|
61
|
+
|
|
62
|
+
const onData = (char) => {
|
|
63
|
+
switch (char) {
|
|
64
|
+
case '\n':
|
|
65
|
+
case '\r':
|
|
66
|
+
case '\u0004': // Ctrl+D
|
|
67
|
+
stdin.setRawMode(false);
|
|
68
|
+
stdin.pause();
|
|
69
|
+
stdin.removeListener('data', onData);
|
|
70
|
+
process.stdout.write('\n');
|
|
71
|
+
resolve(pin);
|
|
72
|
+
break;
|
|
73
|
+
case '\u0003': // Ctrl+C
|
|
74
|
+
stdin.setRawMode(false);
|
|
75
|
+
stdin.pause();
|
|
76
|
+
stdin.removeListener('data', onData);
|
|
77
|
+
process.stdout.write('\n');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
break;
|
|
80
|
+
case '\u007f': // Backspace
|
|
81
|
+
if (pin.length > 0) {
|
|
82
|
+
pin = pin.slice(0, -1);
|
|
83
|
+
process.stdout.write('\b \b');
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
if (char >= '0' && char <= '9' && pin.length < 4) {
|
|
88
|
+
pin += char;
|
|
89
|
+
process.stdout.write('*');
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
stdin.on('data', onData);
|
|
96
|
+
} else {
|
|
97
|
+
// Non-interactive mode (piped input)
|
|
98
|
+
rl.question(message, (answer) => {
|
|
99
|
+
resolve(answer.trim());
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Prompt for yes/no confirmation
|
|
107
|
+
*/
|
|
108
|
+
async promptConfirm(message) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const rl = this.initReadline();
|
|
111
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
112
|
+
resolve(answer.toLowerCase().startsWith('y'));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Setup admin PIN
|
|
119
|
+
*/
|
|
120
|
+
async setupAdminPin() {
|
|
121
|
+
try {
|
|
122
|
+
console.log('\nš Setting up Admin PIN Protection');
|
|
123
|
+
console.log('This will require a 4-digit PIN for administrative operations.');
|
|
124
|
+
|
|
125
|
+
const confirm = await this.promptConfirm('Do you want to enable admin PIN protection?');
|
|
126
|
+
if (!confirm) {
|
|
127
|
+
console.log('Admin PIN protection setup cancelled.');
|
|
128
|
+
this.closeReadline();
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let pin1, pin2;
|
|
133
|
+
do {
|
|
134
|
+
pin1 = await this.promptPin('Enter a 4-digit PIN: ');
|
|
135
|
+
|
|
136
|
+
if (!/^\d{4}$/.test(pin1)) {
|
|
137
|
+
console.log('ā PIN must be exactly 4 digits. Please try again.');
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pin2 = await this.promptPin('Confirm PIN: ');
|
|
142
|
+
|
|
143
|
+
if (pin1 !== pin2) {
|
|
144
|
+
console.log('ā PINs do not match. Please try again.');
|
|
145
|
+
}
|
|
146
|
+
} while (pin1 !== pin2 || !/^\d{4}$/.test(pin1));
|
|
147
|
+
|
|
148
|
+
await this.adminAuth.initialize();
|
|
149
|
+
const success = await this.adminAuth.setupPin(pin1);
|
|
150
|
+
|
|
151
|
+
if (success) {
|
|
152
|
+
console.log('ā
Admin PIN protection enabled successfully!');
|
|
153
|
+
console.log('ā ļø Remember your PIN - it cannot be recovered if lost.');
|
|
154
|
+
SecurityUtils.logSecurityEvent('admin_pin_setup_cli', 'info', 'Admin PIN setup completed via CLI');
|
|
155
|
+
} else {
|
|
156
|
+
console.log('ā Failed to setup admin PIN protection.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.closeReadline();
|
|
160
|
+
return success;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('ā Error setting up admin PIN:', error.message);
|
|
163
|
+
this.closeReadline();
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Authenticate admin user
|
|
170
|
+
*/
|
|
171
|
+
async authenticateAdmin(operation = 'administrative operation') {
|
|
172
|
+
try {
|
|
173
|
+
await this.adminAuth.initialize();
|
|
174
|
+
|
|
175
|
+
const authRequired = await this.adminAuth.isAuthRequired();
|
|
176
|
+
if (!authRequired) {
|
|
177
|
+
return true; // No authentication required
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(`\nš Admin authentication required for: ${operation}`);
|
|
181
|
+
|
|
182
|
+
let attempts = 0;
|
|
183
|
+
const maxAttempts = 3;
|
|
184
|
+
|
|
185
|
+
while (attempts < maxAttempts) {
|
|
186
|
+
const pin = await this.promptPin('Enter admin PIN: ');
|
|
187
|
+
|
|
188
|
+
if (!/^\d{4}$/.test(pin)) {
|
|
189
|
+
console.log('ā Invalid PIN format. PIN must be 4 digits.');
|
|
190
|
+
attempts++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const isValid = await this.adminAuth.verifyPin(pin);
|
|
195
|
+
|
|
196
|
+
if (isValid) {
|
|
197
|
+
console.log('ā
Authentication successful!');
|
|
198
|
+
this.closeReadline();
|
|
199
|
+
return true;
|
|
200
|
+
} else {
|
|
201
|
+
attempts++;
|
|
202
|
+
const remaining = maxAttempts - attempts;
|
|
203
|
+
if (remaining > 0) {
|
|
204
|
+
console.log(`ā Invalid PIN. ${remaining} attempts remaining.`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log('ā Authentication failed. Access denied.');
|
|
210
|
+
SecurityUtils.logSecurityEvent('admin_auth_failed_cli', 'warning', `Admin authentication failed after ${maxAttempts} attempts`);
|
|
211
|
+
this.closeReadline();
|
|
212
|
+
return false;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('ā Authentication error:', error.message);
|
|
215
|
+
this.closeReadline();
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Disable admin authentication
|
|
222
|
+
*/
|
|
223
|
+
async disableAdminAuth() {
|
|
224
|
+
try {
|
|
225
|
+
await this.adminAuth.initialize();
|
|
226
|
+
|
|
227
|
+
const authRequired = await this.adminAuth.isAuthRequired();
|
|
228
|
+
if (!authRequired) {
|
|
229
|
+
console.log('Admin PIN protection is not currently enabled.');
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log('\nš Disabling Admin PIN Protection');
|
|
234
|
+
|
|
235
|
+
// Require authentication to disable
|
|
236
|
+
const authenticated = await this.authenticateAdmin('disable admin protection');
|
|
237
|
+
if (!authenticated) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const confirm = await this.promptConfirm('Are you sure you want to disable admin PIN protection?');
|
|
242
|
+
if (!confirm) {
|
|
243
|
+
console.log('Operation cancelled.');
|
|
244
|
+
this.closeReadline();
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const success = await this.adminAuth.disableAuth();
|
|
249
|
+
|
|
250
|
+
if (success) {
|
|
251
|
+
console.log('ā
Admin PIN protection disabled.');
|
|
252
|
+
SecurityUtils.logSecurityEvent('admin_auth_disabled_cli', 'info', 'Admin PIN protection disabled via CLI');
|
|
253
|
+
} else {
|
|
254
|
+
console.log('ā Failed to disable admin PIN protection.');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.closeReadline();
|
|
258
|
+
return success;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error('ā Error disabling admin protection:', error.message);
|
|
261
|
+
this.closeReadline();
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Show admin status
|
|
268
|
+
*/
|
|
269
|
+
async showAdminStatus() {
|
|
270
|
+
try {
|
|
271
|
+
await this.adminAuth.initialize();
|
|
272
|
+
|
|
273
|
+
const authRequired = await this.adminAuth.isAuthRequired();
|
|
274
|
+
|
|
275
|
+
console.log('\nš Admin Protection Status');
|
|
276
|
+
console.log('=' .repeat(30));
|
|
277
|
+
|
|
278
|
+
if (authRequired) {
|
|
279
|
+
console.log('Status: ā
ENABLED');
|
|
280
|
+
console.log('Protection: 4-digit PIN required for admin operations');
|
|
281
|
+
console.log('Lockout: 3 failed attempts = 15 minute lockout');
|
|
282
|
+
} else {
|
|
283
|
+
console.log('Status: ā DISABLED');
|
|
284
|
+
console.log('Protection: No authentication required');
|
|
285
|
+
console.log('Risk: Administrative operations are unprotected');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return authRequired;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error('ā Error checking admin status:', error.message);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Check if operation requires admin authentication
|
|
297
|
+
*/
|
|
298
|
+
static requiresAdminAuth(operation) {
|
|
299
|
+
const adminOperations = [
|
|
300
|
+
'complete',
|
|
301
|
+
'manage',
|
|
302
|
+
'init',
|
|
303
|
+
'bulk-update',
|
|
304
|
+
'delete-language',
|
|
305
|
+
'reset-translations',
|
|
306
|
+
'delete',
|
|
307
|
+
'workflow'
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
return adminOperations.includes(operation);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Static method to check if operation requires auth (alias)
|
|
315
|
+
*/
|
|
316
|
+
static requiresAuth(operation) {
|
|
317
|
+
return AdminCLI.requiresAdminAuth(operation);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Static method to authenticate
|
|
322
|
+
*/
|
|
323
|
+
static async authenticate(operation = 'administrative operation') {
|
|
324
|
+
const cli = new AdminCLI();
|
|
325
|
+
return await cli.authenticateAdmin(operation);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Static method to setup admin
|
|
330
|
+
*/
|
|
331
|
+
static async setupAdmin() {
|
|
332
|
+
const cli = new AdminCLI();
|
|
333
|
+
return await cli.setupAdminPin();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Static method to disable admin
|
|
338
|
+
*/
|
|
339
|
+
static async disableAdmin() {
|
|
340
|
+
const cli = new AdminCLI();
|
|
341
|
+
return await cli.disableAdminAuth();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Static method to show status
|
|
346
|
+
*/
|
|
347
|
+
static async showStatus() {
|
|
348
|
+
const cli = new AdminCLI();
|
|
349
|
+
return await cli.showAdminStatus();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = AdminCLI;
|