UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

438 lines 18.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultSecureAccountManager = exports.SecureAccountManager = void 0; exports.createSecureAccountManager = createSecureAccountManager; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const account_types_1 = require("../types/account.types"); const account_encryption_1 = require("./account-encryption"); const file_permission_manager_1 = require("./file-permission-manager"); const config_version_manager_1 = require("./config-version-manager"); const config_recovery_manager_1 = require("./config-recovery-manager"); const DEFAULT_CONFIG = { version: '1.0', configDir: '.polyv', configFileName: 'accounts.json', filePermissions: 0o600, dirPermissions: 0o700 }; class SecureAccountManager { constructor(configPath, masterKey) { if (configPath) { this.configPath = configPath.endsWith('.json') ? configPath : path.join(configPath, DEFAULT_CONFIG.configFileName); } else { this.configPath = this.getDefaultConfigPath(); } this.encryption = new account_encryption_1.AccountEncryptionImpl(masterKey); this.permissionManager = new file_permission_manager_1.FilePermissionManager(); this.versionManager = new config_version_manager_1.ConfigVersionManager(); this.recoveryManager = new config_recovery_manager_1.ConfigRecoveryManager(); } getDefaultConfigPath() { const homeDir = process.env['HOME'] || os.homedir(); const configDir = path.join(homeDir, DEFAULT_CONFIG.configDir); return path.join(configDir, DEFAULT_CONFIG.configFileName); } ensureSecureConfigDir() { const configDir = path.dirname(this.configPath); const dirResult = this.permissionManager.ensureSecureDirectory(configDir); if (!dirResult.success) { throw new Error(`Failed to create secure configuration directory: ${dirResult.message}`); } } loadAccountsStore() { try { if (!fs.existsSync(this.configPath)) { return this.createEmptySecureStore(); } const integrity = this.recoveryManager.checkIntegrity(this.configPath); if (!integrity.isValid) { throw new Error(`Configuration file integrity check failed: ${integrity.message}`); } const fileContent = fs.readFileSync(this.configPath, 'utf8'); const store = JSON.parse(fileContent); const versionCheck = this.versionManager.validateVersion(store); if (!versionCheck.isValid && versionCheck.compatibility === 'incompatible') { throw new Error(`Incompatible configuration version: ${versionCheck.message}`); } if (this.versionManager.isMigrationRequired(store)) { return this.migrateConfiguration(store); } this.updateSecurityMetadata(store); return store; } catch (error) { if (error instanceof SyntaxError) { throw new Error(`Invalid JSON in accounts configuration file: ${error.message}`); } throw new Error(`Failed to load accounts configuration: ${error instanceof Error ? error.message : 'Unknown error'}`); } } saveAccountsStore(store) { try { this.ensureSecureConfigDir(); store.metadata.updatedAt = new Date().toISOString(); this.updateSecurityMetadata(store); if (fs.existsSync(this.configPath)) { const backupPath = this.recoveryManager.createBackup(this.configPath); if (backupPath) { store.metadata.lastBackup = new Date().toISOString(); } } const tempPath = `${this.configPath}.tmp`; fs.writeFileSync(tempPath, JSON.stringify(store, null, 2), 'utf8'); const permissionResult = this.permissionManager.setSecurePermissions(tempPath, false); if (!permissionResult.success) { fs.unlinkSync(tempPath); throw new Error(`Failed to set secure permissions: ${permissionResult.message}`); } fs.renameSync(tempPath, this.configPath); } catch (error) { throw new Error(`Failed to save accounts configuration: ${error instanceof Error ? error.message : 'Unknown error'}`); } } createEmptySecureStore() { const now = new Date().toISOString(); return { version: DEFAULT_CONFIG.version, accounts: {}, metadata: { createdAt: now, updatedAt: now, keySource: this.encryption.getKeySource(), security: { encryptionVersion: '1.0', keySource: this.encryption.getKeySource(), lastSecurityCheck: now } } }; } updateSecurityMetadata(store) { if (!store.metadata.security) { store.metadata.security = { encryptionVersion: '1.0', keySource: this.encryption.getKeySource() }; } store.metadata.security.lastSecurityCheck = new Date().toISOString(); store.metadata.keySource = this.encryption.getKeySource(); } migrateConfiguration(store) { const currentVersion = this.versionManager.getCurrentVersion(); const detectedVersion = this.versionManager.detectVersion(store); const migrationResult = this.versionManager.migrateConfiguration(store, detectedVersion, currentVersion); if (!migrationResult.success) { throw new Error(`Configuration migration failed: ${migrationResult.message}`); } const migratedStore = migrationResult.migratedConfig; if (migrationResult.steps.some(step => step.includes('encryption'))) { this.reencryptAccounts(migratedStore); } return migratedStore; } reencryptAccounts(store) { for (const accountName in store.accounts) { const account = store.accounts[accountName]; if (account && (account._needsReencryption || account._needsEncryption)) { account.appSecret = { algorithm: 'aes-256-gcm', iv: '', authTag: '', encrypted: '' }; account._migrationRequired = true; } } } addAccount(name, appId, appSecret, userId) { try { this.validateAccountConfig({ name, appId, appSecret }); const store = this.loadAccountsStore(); if (store.accounts[name]) { return { success: false, message: `Account '${name}' already exists. Use a different name or remove the existing account first.` }; } const encryptedSecret = this.encryption.encrypt(appSecret); const now = new Date().toISOString(); const accountConfig = { name, appId, appSecret: encryptedSecret, ...(userId && { userId }), createdAt: now, updatedAt: now }; store.accounts[name] = accountConfig; this.saveAccountsStore(store); const displayAccount = { ...accountConfig, appSecret: '[encrypted]' }; return { success: true, message: `Account '${name}' added successfully with enhanced security.`, account: displayAccount }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : 'Unknown error occurred' }; } } getAccount(name) { try { const store = this.loadAccountsStore(); const account = store.accounts[name]; if (!account) { return null; } if (account._migrationRequired) { throw new Error(`Account '${name}' requires migration. Please re-add this account.`); } let decryptedSecret; if (typeof account.appSecret === 'object' && account.appSecret.algorithm === 'aes-256-gcm') { decryptedSecret = this.encryption.decrypt(account.appSecret); } else if (typeof account.appSecret === 'string') { throw new Error(`Account '${name}' uses legacy encryption format. Please re-add this account.`); } else { throw new Error(`Account '${name}' has invalid encryption format.`); } return { name: account.name, appId: account.appId, appSecret: decryptedSecret, ...(account.userId && { userId: account.userId }), createdAt: account.createdAt, updatedAt: account.updatedAt }; } catch (error) { throw new Error(`Failed to get account '${name}': ${error instanceof Error ? error.message : 'Unknown error'}`); } } removeAccount(name) { try { const store = this.loadAccountsStore(); if (!store.accounts[name]) { return { success: false, message: `Account '${name}' not found.` }; } delete store.accounts[name]; this.saveAccountsStore(store); return { success: true, message: `Account '${name}' removed successfully.` }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : 'Unknown error occurred' }; } } listAccounts() { try { const store = this.loadAccountsStore(); return Object.values(store.accounts).map(account => ({ name: account.name, appId: account.appId, ...(account.userId && { userId: account.userId }), createdAt: account.createdAt, updatedAt: account.updatedAt })); } catch (error) { throw new Error(`Failed to list accounts: ${error instanceof Error ? error.message : 'Unknown error'}`); } } accountExists(name) { try { const store = this.loadAccountsStore(); return name in store.accounts; } catch { return false; } } getSecurityContext() { try { const store = this.loadAccountsStore(); const permissionValidation = this.permissionManager.validatePermissions(this.configPath); return { encryptionEnabled: true, keySource: this.encryption.getKeySource(), configVersion: store.version, lastSecurityCheck: new Date(), permissionsValid: permissionValidation.isValid }; } catch { return { encryptionEnabled: false, keySource: 'generated', configVersion: 'unknown', lastSecurityCheck: new Date(), permissionsValid: false }; } } validateSecurity() { const issues = []; const recommendations = []; try { const integrity = this.recoveryManager.checkIntegrity(this.configPath); if (!integrity.isValid) { issues.push(`Configuration integrity: ${integrity.message}`); recommendations.push('Run: polyv-cli config recover'); } const permissionValidation = this.permissionManager.validatePermissions(this.configPath); if (!permissionValidation.isValid) { issues.push(`File permissions: ${permissionValidation.message}`); recommendations.push(...permissionValidation.recommendations); } if (!this.encryption.testEncryption()) { issues.push('Encryption system is not functioning properly'); recommendations.push('Check POLYV_MASTER_KEY environment variable'); } const store = this.loadAccountsStore(); const versionCheck = this.versionManager.validateVersion(store); if (!versionCheck.isValid) { issues.push(`Version compatibility: ${versionCheck.message}`); recommendations.push(...versionCheck.requiredActions); } } catch (error) { issues.push(`Security validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); recommendations.push('Check configuration file and permissions'); } return { isSecure: issues.length === 0, issues, recommendations }; } getConfigPath() { return this.configPath; } performSecurityMaintenance() { const actionsPerformed = []; const warnings = []; try { const permissionResult = this.permissionManager.repairConfigurationFile(this.configPath); if (permissionResult.success) { actionsPerformed.push('Fixed file permissions'); } else { warnings.push(`Could not fix permissions: ${permissionResult.message}`); } const backupPath = this.recoveryManager.createBackup(this.configPath); if (backupPath) { actionsPerformed.push(`Created backup: ${backupPath}`); } else { warnings.push('Could not create backup'); } if (fs.existsSync(this.configPath)) { const store = this.loadAccountsStore(); this.updateSecurityMetadata(store); this.saveAccountsStore(store); actionsPerformed.push('Updated security metadata'); } return { success: true, actionsPerformed, warnings }; } catch (error) { return { success: false, actionsPerformed, warnings: [`Security maintenance failed: ${error instanceof Error ? error.message : 'Unknown error'}`] }; } } validateAccountConfig(config) { const { name, appId, appSecret } = config; if (!name || typeof name !== 'string') { throw new Error('Account name is required and must be a string'); } if (!account_types_1.AccountConfigValidation.name.pattern.test(name)) { throw new Error('Account name can only contain letters, numbers, underscores, and hyphens'); } if (name.length > account_types_1.AccountConfigValidation.name.maxLength) { throw new Error(`Account name must be ${account_types_1.AccountConfigValidation.name.maxLength} characters or less`); } if (!appId || typeof appId !== 'string') { throw new Error('App ID is required and must be a string'); } if (!account_types_1.AccountConfigValidation.appId.pattern.test(appId)) { throw new Error('App ID can only contain letters and numbers'); } if (appId.length < account_types_1.AccountConfigValidation.appId.minLength || appId.length > account_types_1.AccountConfigValidation.appId.maxLength) { throw new Error(`App ID must be between ${account_types_1.AccountConfigValidation.appId.minLength} and ${account_types_1.AccountConfigValidation.appId.maxLength} characters`); } if (!appSecret || typeof appSecret !== 'string') { throw new Error('App Secret is required and must be a string'); } if (appSecret.length < account_types_1.AccountConfigValidation.appSecret.minLength || appSecret.length > account_types_1.AccountConfigValidation.appSecret.maxLength) { throw new Error(`App Secret must be between ${account_types_1.AccountConfigValidation.appSecret.minLength} and ${account_types_1.AccountConfigValidation.appSecret.maxLength} characters`); } } } exports.SecureAccountManager = SecureAccountManager; function createSecureAccountManager(configPath, masterKey) { return new SecureAccountManager(configPath, masterKey); } exports.defaultSecureAccountManager = createSecureAccountManager(); //# sourceMappingURL=secure-account-manager.js.map