UNPKG

i18ntk

Version:

i18ntk (i18n Toolkit) - A comprehensive, enterprise-grade internationalization (i18n) management toolkit for JavaScript/TypeScript projects with advanced analysis, validation, and automation features

357 lines (307 loc) 9.75 kB
const readline = require('readline'); const i18n = require('./i18n-helper'); const AdminAuth = require('./admin-auth'); const SecurityUtils = require('./security'); /** * CLI Helper for Admin Authentication */ class AdminCLI { constructor() { this.adminAuth = new AdminAuth(); this.rl = null; } /** * Initialize readline interface */ initReadline() { if (!this.rl) { this.rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true, historySize: 0 }); } return this.rl; } /** * Close readline interface */ closeReadline() { if (this.rl) { this.rl.close(); this.rl = null; } } /** * Prompt for PIN input (hidden) */ async promptPin(message = null) { if (!message) message = i18n.t('adminCli.enterPin'); return new Promise((resolve) => { const rl = this.initReadline(); // Check if stdin is a TTY (interactive terminal) const stdin = process.stdin; const isTTY = stdin && stdin.isTTY; if (isTTY) { // Interactive mode with hidden input let pin = ''; process.stdout.write(message); // Ensure raw mode is properly set and cleaned up stdin.setRawMode(true); stdin.resume(); stdin.setEncoding('utf8'); const cleanup = () => { stdin.setRawMode(false); stdin.pause(); stdin.removeAllListeners('data'); }; const onData = (char) => { switch (char) { case '\n': case '\r': case '\u0004': // Ctrl+D cleanup(); process.stdout.write('\n'); resolve(pin); break; case '\u0003': // Ctrl+C cleanup(); process.stdout.write('\n'); process.exit(1); break; case '\u007f': // Backspace if (pin.length > 0) { pin = pin.slice(0, -1); process.stdout.write('\b \b'); } break; default: if (char >= '0' && char <= '9' && pin.length < 6) { pin += char; process.stdout.write('*'); } break; } }; stdin.on('data', onData); } else { // Non-interactive mode (piped input) // Close readline after use to prevent hanging rl.question(message, (answer) => { this.closeReadline(); resolve(answer.trim()); }); } }); } /** * Prompt for yes/no confirmation */ async promptConfirm(message) { return new Promise((resolve) => { const rl = this.initReadline(); rl.question(`${message} (y/N): `, (answer) => { resolve(answer.toLowerCase().startsWith('y')); }); }); } /** * Setup admin PIN */ async setupAdminPin() { try { console.log(i18n.t('adminCli.setupPinProtectionTitle')); console.log(i18n.t('adminCli.setupPinProtectionDescription')); const confirm = await this.promptConfirm(i18n.t('adminCli.enablePinProtectionPrompt')); if (!confirm) { console.log(i18n.t('adminCli.setupCancelled')); this.closeReadline(); return false; } let pin1, pin2; do { pin1 = await this.promptPin(i18n.t('adminCli.enterPinPrompt')); if (!/^\d{4,6}$/.test(pin1)) { console.log(i18n.t('adminCli.pinFormatError')); continue; } pin2 = await this.promptPin(i18n.t('adminCli.confirmPinPrompt')); if (pin1 !== pin2) { console.log(i18n.t('adminCli.pinMismatchError')); } } while (pin1 !== pin2 || !/^\d{4,6}$/.test(pin1)); await this.adminAuth.initialize(); const success = await this.adminAuth.setupPin(pin1); if (success) { console.log(i18n.t('adminCli.pinProtectionEnabledSuccess')); console.log(i18n.t('adminCli.pinRecoveryWarning')); SecurityUtils.logSecurityEvent(i18n.t('adminCli.adminPinSetupCli'), 'info', 'Admin PIN setup completed via CLI'); } else { console.log(i18n.t('adminCli.setupPinProtectionFailed')); } this.closeReadline(); return success; } catch (error) { console.error(i18n.t('adminCli.errorSettingUpPin', { message: error.message })); this.closeReadline(); return false; } } /** * Authenticate admin user */ async authenticateAdmin(operation = 'administrative operation') { try { await this.adminAuth.initialize(); const authRequired = await this.adminAuth.isAuthRequired(); if (!authRequired) { return true; // No authentication required } console.log(i18n.t('adminCli.authRequiredForOperation', { operation })); let attempts = 0; const maxAttempts = 3; while (attempts < maxAttempts) { const pin = await this.promptPin(); if (!/^\d{4,6}$/.test(pin)) { console.log(i18n.t('adminCli.invalidPinFormat')); attempts++; continue; } const isValid = await this.adminAuth.verifyPin(pin); if (isValid) { console.log(i18n.t('adminCli.authenticationSuccess')); this.closeReadline(); return true; } else { attempts++; const remaining = maxAttempts - attempts; if (remaining > 0) { console.log(i18n.t('adminCli.invalidPinAttemptsRemaining', { remaining })); } } } console.log(i18n.t('adminCli.authenticationFailedAccessDenied')); SecurityUtils.logSecurityEvent(i18n.t('adminCli.adminAuthFailedCli'), 'warning', `Admin authentication failed after ${maxAttempts} attempts`); this.closeReadline(); return false; } catch (error) { console.error(i18n.t('adminCli.authenticationError', { message: error.message })); this.closeReadline(); return false; } } /** * Disable admin authentication */ async disableAdminAuth() { try { await this.adminAuth.initialize(); const authRequired = await this.adminAuth.isAuthRequired(); if (!authRequired) { console.log(i18n.t('adminCli.pinProtectionNotEnabled')); return true; } console.log(i18n.t('adminCli.disablingPinProtectionTitle')); // Require authentication to disable const authenticated = await this.authenticateAdmin('disable admin protection'); if (!authenticated) { return false; } const confirm = await this.promptConfirm(i18n.t('adminCli.confirmDisablePinProtection')); if (!confirm) { console.log(i18n.t('adminCli.operationCancelled')); this.closeReadline(); return false; } const success = await this.adminAuth.disableAuth(); if (success) { console.log(i18n.t('adminCli.pinProtectionDisabledSuccess')); SecurityUtils.logSecurityEvent(i18n.t('adminCli.adminAuthDisabledCli'), 'info', 'Admin PIN protection disabled via CLI'); } else { console.log(i18n.t('adminCli.disablePinProtectionFailed')); } this.closeReadline(); return success; } catch (error) { console.error(i18n.t('adminCli.errorDisablingPinProtection', { message: error.message })); this.closeReadline(); return false; } } /** * Show admin status */ async showAdminStatus() { try { await this.adminAuth.initialize(); const authRequired = await this.adminAuth.isAuthRequired(); console.log(i18n.t('adminCli.adminProtectionStatusTitle')); console.log('=' .repeat(30)); if (authRequired) { console.log(i18n.t('adminCli.statusEnabled')); console.log(i18n.t('adminCli.protectionDetails')); console.log(i18n.t('adminCli.lockoutDetails')); } else { console.log(i18n.t('adminCli.statusDisabled')); console.log(i18n.t('adminCli.noAuthRequired')); console.log(i18n.t('adminCli.unprotectedRisk')); } return authRequired; } catch (error) { console.error(i18n.t('adminCli.errorCheckingAdminStatus', { message: error.message })); return false; } } /** * Check if operation requires admin authentication */ static requiresAdminAuth(operation) { const adminOperations = [ 'complete', 'manage', 'init', 'bulk-update', 'delete-language', 'reset-translations', 'delete', 'workflow' ]; return adminOperations.includes(operation); } /** * Static method to check if operation requires auth (alias) */ static requiresAuth(operation) { return AdminCLI.requiresAdminAuth(operation); } /** * Static method to authenticate */ static async authenticate(operation = 'administrative operation') { const cli = new AdminCLI(); return await cli.authenticateAdmin(operation); } /** * Static method to setup admin */ static async setupAdmin() { const cli = new AdminCLI(); return await cli.setupAdminPin(); } /** * Static method to disable admin */ static async disableAdmin() { const cli = new AdminCLI(); return await cli.disableAdminAuth(); } /** * Static method to show status */ static async showStatus() { const cli = new AdminCLI(); return await cli.showAdminStatus(); } } module.exports = AdminCLI;