"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TrusysSecretsVaultService = void 0;
const crypto_1 = require("crypto");
const util_1 = require("util");
const uuid_1 = require("uuid");
const drizzle_orm_1 = require("drizzle-orm");
const database_1 = require("../../database");
const tables_1 = require("../../database/tables");
const logger_1 = __importDefault(require("../../logger"));
const scryptAsync = (0, util_1.promisify)(crypto_1.scrypt);
/**
 * Service for managing secrets in the Trusys Secrets Vault (database)
 * All secrets are encrypted before storage and decrypted when retrieved
 */
class TrusysSecretsVaultService {
    constructor(masterSecretKey) {
        if (!masterSecretKey) {
            throw new Error('MASTER_SECRET_KEY is required for TrusysSecretsVaultService');
        }
        this.masterSecretKey = masterSecretKey;
    }
    /**
     * Derive a key from the master secret key using scrypt
     */
    async deriveKey(salt) {
        const keyLength = 32; // 32 bytes for AES-256
        return (await scryptAsync(this.masterSecretKey, salt, keyLength));
    }
    /**
     * Encrypt a value using AES-256-GCM
     */
    async encryptValue(value) {
        try {
            const salt = (0, crypto_1.randomBytes)(16);
            const key = await this.deriveKey(salt);
            const iv = (0, crypto_1.randomBytes)(12); // 12 bytes for GCM
            const cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', key, iv);
            let encrypted = cipher.update(value, 'utf8');
            encrypted = Buffer.concat([encrypted, cipher.final()]);
            const authTag = cipher.getAuthTag();
            // Combine salt + iv + authTag + encrypted data
            const combined = Buffer.concat([salt, iv, authTag, encrypted]);
            return combined.toString('base64');
        }
        catch (error) {
            logger_1.default.error(`Error encrypting value: ${error}`);
            throw new Error(`Failed to encrypt value: ${error}`);
        }
    }
    /**
     * Decrypt a value using AES-256-GCM
     */
    async decryptValue(encryptedValue) {
        try {
            const combined = Buffer.from(encryptedValue, 'base64');
            // Extract components
            const salt = combined.slice(0, 16);
            const iv = combined.slice(16, 28);
            const authTag = combined.slice(28, 44);
            const encrypted = combined.slice(44);
            const key = await this.deriveKey(salt);
            const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', key, iv);
            decipher.setAuthTag(authTag);
            let decrypted = decipher.update(encrypted);
            decrypted = Buffer.concat([decrypted, decipher.final()]);
            return decrypted.toString('utf8');
        }
        catch (error) {
            logger_1.default.error(`Error decrypting value: ${error}`);
            throw new Error(`Failed to decrypt value: ${error}`);
        }
    }
    /**
     * Encrypt all values in a secrets object
     */
    async encryptSecrets(secrets) {
        const encrypted = {};
        for (const [key, value] of Object.entries(secrets)) {
            if (typeof value === 'string') {
                encrypted[key] = await this.encryptValue(value);
            }
            else {
                // For non-string values, convert to string first
                encrypted[key] = await this.encryptValue(String(value));
            }
        }
        return encrypted;
    }
    /**
     * Decrypt all values in a secrets object
     */
    async decryptSecrets(encryptedSecrets) {
        const decrypted = {};
        for (const [key, encryptedValue] of Object.entries(encryptedSecrets)) {
            if (typeof encryptedValue === 'string') {
                decrypted[key] = await this.decryptValue(encryptedValue);
            }
            else {
                decrypted[key] = encryptedValue;
            }
        }
        return decrypted;
    }
    /**
     * Get a single secret value by secret name and key
     */
    async get(secretName, secretKey) {
        try {
            const db = (0, database_1.getDb)();
            const [secret] = await db
                .select()
                .from(tables_1.trusysSecretsVaultTable)
                .where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.name, secretName))
                .limit(1);
            if (!secret) {
                throw new Error(`Secret ${secretName} not found`);
            }
            const decryptedSecrets = await this.decryptSecrets(secret.secrets);
            const value = decryptedSecrets[secretKey];
            if (value === undefined) {
                throw new Error(`Secret key ${secretKey} not found in ${secretName}`);
            }
            return value;
        }
        catch (error) {
            logger_1.default.error(`Error getting secret ${secretName}.${secretKey}: ${error}`);
            throw error;
        }
    }
    /**
     * Get all secrets for a given secret name
     */
    async getAll(secretName) {
        try {
            const db = (0, database_1.getDb)();
            const [secret] = await db
                .select()
                .from(tables_1.trusysSecretsVaultTable)
                .where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.name, secretName))
                .limit(1);
            if (!secret) {
                throw new Error(`Secret ${secretName} not found`);
            }
            return await this.decryptSecrets(secret.secrets);
        }
        catch (error) {
            logger_1.default.error(`Error getting all secrets for ${secretName}: ${error}`);
            throw error;
        }
    }
    /**
     * Check if a secret exists
     */
    async exist(secretName) {
        try {
            const db = (0, database_1.getDb)();
            const [secret] = await db
                .select()
                .from(tables_1.trusysSecretsVaultTable)
                .where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.name, secretName))
                .limit(1);
            return !!secret;
        }
        catch (error) {
            logger_1.default.error(`Error checking existence of secret ${secretName}: ${error}`);
            return false;
        }
    }
    /**
     * Create a new secret
     */
    async create(secretName, secrets) {
        try {
            const db = (0, database_1.getDb)();
            const encryptedSecrets = await this.encryptSecrets(secrets);
            const id = (0, uuid_1.v4)();
            await db.insert(tables_1.trusysSecretsVaultTable).values({
                id,
                name: secretName,
                secrets: encryptedSecrets,
            });
            return secrets; // Return the original unencrypted secrets
        }
        catch (error) {
            logger_1.default.error(`Error creating secret ${secretName}: ${error}`);
            throw error;
        }
    }
    /**
     * Update an existing secret (merges with existing secrets)
     */
    async update(secretName, secrets) {
        try {
            const db = (0, database_1.getDb)();
            const [existing] = await db
                .select()
                .from(tables_1.trusysSecretsVaultTable)
                .where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.name, secretName))
                .limit(1);
            if (!existing) {
                throw new Error(`Secret ${secretName} not found`);
            }
            // Decrypt existing secrets, merge with new ones, then re-encrypt
            const existingDecrypted = await this.decryptSecrets(existing.secrets);
            const mergedSecrets = { ...existingDecrypted, ...secrets };
            const encryptedSecrets = await this.encryptSecrets(mergedSecrets);
            await db
                .update(tables_1.trusysSecretsVaultTable)
                .set({
                secrets: encryptedSecrets,
                updatedAt: new Date(),
            })
                .where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.name, secretName));
            return mergedSecrets; // Return the merged unencrypted secrets
        }
        catch (error) {
            logger_1.default.error(`Error updating secret ${secretName}: ${error}`);
            throw error;
        }
    }
    /**
     * Delete a secret
     */
    async delete(secretName) {
        try {
            const db = (0, database_1.getDb)();
            await db.delete(tables_1.trusysSecretsVaultTable).where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.name, secretName));
        }
        catch (error) {
            logger_1.default.error(`Error deleting secret ${secretName}: ${error}`);
            throw error;
        }
    }
    /**
     * Delete multiple secrets matching a search string
     */
    async deleteMany(searchString) {
        try {
            const db = (0, database_1.getDb)();
            // Note: This is a simple implementation. For better performance with large datasets,
            // you might want to use LIKE queries or full-text search
            const allSecrets = await db.select().from(tables_1.trusysSecretsVaultTable);
            for (const secret of allSecrets) {
                if (secret.name.includes(searchString)) {
                    await db.delete(tables_1.trusysSecretsVaultTable).where((0, drizzle_orm_1.eq)(tables_1.trusysSecretsVaultTable.id, secret.id));
                }
            }
        }
        catch (error) {
            logger_1.default.error(`Error deleting secrets with search string ${searchString}: ${error}`);
            throw error;
        }
    }
}
exports.TrusysSecretsVaultService = TrusysSecretsVaultService;
//# sourceMappingURL=TrusysSecretsVaultService.js.map