polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
438 lines • 18.5 kB
JavaScript
"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-live-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