UNPKG

@vatsdev/encryption-decryption

Version:

A TypeScript library for secure encryption and decryption using Google Cloud KMS and Secret Manager

1,111 lines (1,097 loc) 41.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ConfigurationError: () => ConfigurationError, EncryptionError: () => EncryptionError, EncryptionService: () => encryptionService_default, ErrorCodes: () => ErrorCodes, ValidationError: () => ValidationError, createHash: () => createHash_default, createNewEncryption: () => createNewEncryption, decryptField: () => decryptField, encryptField: () => encryptField, generateDEK: () => generateDEK, handleFieldEncryption: () => handleFieldEncryption, kmsService: () => kmsService_default, secretManagerService: () => secretManagerService_default }); module.exports = __toCommonJS(index_exports); // src/utils/encryptionUtils.ts var import_crypto3 = __toESM(require("crypto")); // src/types/errors.ts var ConfigurationError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "ConfigurationError"; } }; var EncryptionError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "EncryptionError"; } }; var ValidationError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "ValidationError"; } }; var ErrorCodes = { CONFIGURATION: { MISSING_CONFIG: "CONFIGURATION_MISSING_CONFIG", MISSING_ENV_VAR: "CONFIGURATION_MISSING_ENV_VAR", INITIALIZATION_ERROR: "CONFIGURATION_INITIALIZATION_ERROR", INVALID_CREDENTIALS: "CONFIGURATION_INVALID_CREDENTIALS" }, ENCRYPTION: { FIELD_ENCRYPTION_ERROR: "ENCRYPTION_FIELD_ENCRYPTION_ERROR", FIELD_DECRYPTION_ERROR: "ENCRYPTION_FIELD_DECRYPTION_ERROR", DEK_RESOLUTION_ERROR: "ENCRYPTION_DEK_RESOLUTION_ERROR", DEK_DECRYPTION_ERROR: "ENCRYPTION_DEK_DECRYPTION_ERROR", DEK_ENCRYPTION_ERROR: "ENCRYPTION_DEK_ENCRYPTION_ERROR", SECRET_RETRIEVAL_ERROR: "ENCRYPTION_SECRET_RETRIEVAL_ERROR", UNKNOWN_ERROR: "ENCRYPTION_UNKNOWN_ERROR", KEY_DETAILS_ERROR: "ENCRYPTION_KEY_DETAILS_ERROR", KEY_VERSION_ERROR: "ENCRYPTION_KEY_VERSION_ERROR", KEY_CREATION_ERROR: "ENCRYPTION_KEY_CREATION_ERROR", CREATION_ERROR: "ENCRYPTION_CREATION_ERROR" }, VALIDATION: { MISSING_REQUIRED_FIELD: "VALIDATION_MISSING_REQUIRED_FIELD", INVALID_DEK: "VALIDATION_INVALID_DEK" } }; // src/services/kmsService.ts var import_kms2 = require("@google-cloud/kms"); var import_crypto = __toESM(require("crypto")); // src/utils/createKms.ts var createKms = (keyName, environment) => { const keyRingId = `kr-${keyName}-${environment}`; const keyId = `key-${keyName}-${environment}`; return { keyRingId, keyId }; }; var createKms_default = createKms; // src/services/kmsService.ts var import_dotenv = __toESM(require("dotenv")); var import_path = __toESM(require("path")); // src/utils/createKeyRing.ts var import_kms = require("@google-cloud/kms"); async function createKeyRing(projectId, locationId, keyRingId, credentials) { var _a, _b, _c; const client = new import_kms.KeyManagementServiceClient({ credentials }); const parent = client.locationPath(projectId, locationId); const keyRingName = client.keyRingPath(projectId, locationId, keyRingId); try { await client.getKeyRing({ name: keyRingName }); console.log("KeyRing already exists:", keyRingName); const [keys] = await client.listCryptoKeys({ parent: keyRingName }); if (keys.length === 0) { console.log("No keys found inside this KeyRing."); return { latestKeyWithVersion: keyRingName, isNewCreated: false }; } console.log("Getting the list inside the keyRing"); const keysWithVersions = await Promise.all( keys.map(async (key) => { const [versions] = await client.listCryptoKeyVersions({ parent: key.name }); return { keyName: key.name, purpose: key.purpose, versions: versions.map((v) => ({ name: v.name, state: v.state })) }; }) ); const latestKeyWithVersion = ((_b = (_a = keysWithVersions[0]) == null ? void 0 : _a.versions[0]) == null ? void 0 : _b.name) || null; return { keyName: ((_c = keysWithVersions[0]) == null ? void 0 : _c.keyName) || keyRingName, latestKeyWithVersion, isNewCreated: false }; } catch (err) { if (err.code === 5) { console.log("KeyRing does not exist, creating:", keyRingName); await client.createKeyRing({ parent, keyRingId, keyRing: {} }); return { isNewCreated: true }; } else { console.error("Full API error response:", JSON.stringify(err, null, 2)); throw new Error( `Failed to check/create key ring: ${err.message || "Unknown error"}` ); } } } var createKeyRing_default = createKeyRing; // src/services/kmsService.ts import_dotenv.default.config(); var KMSService = class { constructor() { try { const configPath = import_path.default.join(process.cwd(), "config/index.js"); const config = require(configPath); const encryptionConfig = config.get("encryption"); this.environment = encryptionConfig.ENV || "dev"; if (encryptionConfig == null ? void 0 : encryptionConfig.credentials) { if (!encryptionConfig.credentials.client_email || !encryptionConfig.credentials.private_key) { throw new ConfigurationError( "Credentials object is missing required fields (client_email or private_key)", ErrorCodes.CONFIGURATION.INVALID_CREDENTIALS ); } const credentials = encryptionConfig.credentials; this.client = new import_kms2.KeyManagementServiceClient({ credentials: encryptionConfig.credentials, fallback: true }); this.credentials = credentials; this.projectId = encryptionConfig.projectId; this.locationId = encryptionConfig.locationId; } else { this.client = new import_kms2.KeyManagementServiceClient(); this.projectId = process.env.GOOGLE_CLOUD_PROJECT || ""; this.locationId = process.env.KMS_LOCATION_ID || "global"; } if (!this.projectId) { throw new ConfigurationError( "GOOGLE_CLOUD_PROJECT environment variable is required", ErrorCodes.CONFIGURATION.MISSING_ENV_VAR ); } } catch (error) { if (error instanceof ConfigurationError) { throw error; } throw new ConfigurationError( `Failed to initialize KMS service: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.CONFIGURATION.INITIALIZATION_ERROR ); } } createSecretKeyWithEnv(clientName) { return `secret-${clientName}-${this.environment}`; } /** * Creates a new key ring and key in Google Cloud KMS * @param dek - Data Encryption Key to encrypt * @param clientName - Optional name for the KMS key * @returns Promise resolving to encryption result with metadata * @throws {EncryptionError} When key creation or encryption fails */ async createKeyRingAndKey(dek, clientName) { var _a; const { keyRingId, keyId } = createKms_default(clientName || import_crypto.default.randomBytes(16).toString("hex"), this.environment); try { const keyring = await createKeyRing_default( this.projectId, this.locationId, keyRingId, this.credentials || "" ); console.log(`${(keyring == null ? void 0 : keyring.isNewCreated) ? "Creating new" : "Getting Existing"} crypto key`); let keyName; let keyKmsPath; if (keyring.isNewCreated) { const [key] = await this.client.createCryptoKey({ parent: this.client.keyRingPath(this.projectId, this.locationId, keyRingId), cryptoKeyId: keyId, cryptoKey: { purpose: "ENCRYPT_DECRYPT", versionTemplate: { algorithm: "GOOGLE_SYMMETRIC_ENCRYPTION" } } }); const keyVersionName = (_a = key.primary) == null ? void 0 : _a.name; if (!keyVersionName) { throw new EncryptionError( "Failed to retrieve primary key version", ErrorCodes.ENCRYPTION.KEY_VERSION_ERROR ); } const keyDetailsArray = keyVersionName.split("/"); const keyVersion = keyDetailsArray[keyDetailsArray.length - 1]; if (!keyVersion) { throw new EncryptionError( "Failed to retrieve key version", ErrorCodes.ENCRYPTION.KEY_VERSION_ERROR ); } if (!key.name) { throw new EncryptionError( "Failed to create crypto key: key name is undefined", ErrorCodes.ENCRYPTION.KEY_CREATION_ERROR ); } keyName = key.name; keyKmsPath = keyVersionName; } else { keyName = keyring.keyName; keyKmsPath = keyring.latestKeyWithVersion; } const [encryptResponse] = await this.client.encrypt({ name: keyName, plaintext: dek }); return { encryptedDEK: encryptResponse.ciphertext, kmsPath: keyKmsPath || "" }; } catch (error) { if (error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to create key ring and key: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.KEY_CREATION_ERROR ); } } /** * Checks if a KMS key exists for the given client name * @param clientName - Name of the client to check for existing key * @returns Promise resolving to KMS path if key exists, null if not found * @throws {EncryptionError} When key check fails */ async getExistingKMSPath(clientName) { var _a; try { if (!clientName) { throw new ValidationError( "Client name is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const { keyRingId, keyId } = createKms_default(clientName, this.environment); try { const keyName = this.client.cryptoKeyPath(this.projectId, this.locationId, keyRingId, keyId); const [key] = await this.client.getCryptoKey({ name: keyName }); if ((_a = key.primary) == null ? void 0 : _a.name) { console.log("Found existing KMS key for client:", clientName, "- Key path:", key.primary.name); return key.primary.name; } return null; } catch (error) { if (error.code === 5) { console.log("No existing KMS key found for client:", clientName); return null; } else { throw error; } } } catch (error) { if (error instanceof ValidationError) { throw error; } throw new EncryptionError( `Failed to check existing KMS key: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } } /** * Encrypts a Data Encryption Key (DEK) using Google Cloud KMS * @param dek - Data Encryption Key to encrypt * @param clientName - Optional name for the KMS key * @returns Promise resolving to encryption result with metadata * @throws {EncryptionError} When encryption fails */ async encryptDEK(dek, clientName) { try { if (!dek || dek.length !== 32) { throw new ValidationError( "Invalid DEK: must be 32 bytes for AES-256-GCM", ErrorCodes.VALIDATION.INVALID_DEK ); } return await this.createKeyRingAndKey(dek, clientName); } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to encrypt DEK: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.DEK_ENCRYPTION_ERROR ); } } /** * Decrypts a Data Encryption Key (DEK) using Google Cloud KMS * @param params - Parameters containing encrypted DEK and key metadata * @returns Promise resolving to decrypted DEK * @throws {ValidationError} When input parameters are invalid * @throws {EncryptionError} When decryption fails */ async decryptDEK({ encryptedDEKData, kmsPath }) { try { if (!encryptedDEKData) { throw new ValidationError( "Encrypted DEK data is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } if (!kmsPath) { throw new ValidationError( "Missing required key metadata", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const keyMetadata = kmsPath.split("/").slice(0, 8).join("/"); const [decryptResponse] = await this.client.decrypt({ name: keyMetadata, ciphertext: encryptedDEKData }); const dek = decryptResponse.plaintext; if (dek.length !== 32) { throw new ValidationError( `Invalid DEK length: ${dek.length} bytes. Expected 32 bytes for AES-256-GCM`, ErrorCodes.VALIDATION.INVALID_DEK ); } return dek; } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to decrypt DEK: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.DEK_DECRYPTION_ERROR ); } } }; var kmsService_default = new KMSService(); // src/services/secretManagerService.ts var import_secret_manager = require("@google-cloud/secret-manager"); var import_path2 = __toESM(require("path")); var SecretManagerService = class { constructor() { try { const configPath = import_path2.default.join(process.cwd(), "config/index.js"); const config = require(configPath); const encryptionConfig = config.get("encryption"); if (encryptionConfig == null ? void 0 : encryptionConfig.credentials) { if (!encryptionConfig.credentials.client_email || !encryptionConfig.credentials.private_key) { throw new ConfigurationError( "Credentials object is missing required fields (client_email or private_key)", ErrorCodes.CONFIGURATION.INVALID_CREDENTIALS ); } this.secretManager = new import_secret_manager.SecretManagerServiceClient({ credentials: encryptionConfig.credentials }); this.projectId = encryptionConfig.projectId; } else { this.secretManager = new import_secret_manager.SecretManagerServiceClient(); this.projectId = process.env.GOOGLE_CLOUD_PROJECT || ""; } if (!this.projectId) { throw new ConfigurationError( "GOOGLE_CLOUD_PROJECT environment variable is required", ErrorCodes.CONFIGURATION.MISSING_ENV_VAR ); } } catch (error) { if (error instanceof ConfigurationError) { throw error; } throw new ConfigurationError( `Failed to initialize Secret Manager service: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.CONFIGURATION.INITIALIZATION_ERROR ); } } /** * Checks if a secret exists for a given client by trying to access it directly * @param clientName - Name of the client to check * @returns Promise resolving to secret name if found, null if not found * @throws {EncryptionError} When secret check fails */ async getExistingSecretForClient(clientName) { try { if (!clientName) { throw new ValidationError( "Client name is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const secretId = kmsService_default.createSecretKeyWithEnv(clientName); const secretName = `projects/${this.projectId}/secrets/${secretId}`; try { await this.secretManager.getSecret({ name: secretName }); return secretName; } catch (error) { if (error.code === 5) { return null; } else { throw error; } } } catch (error) { if (error instanceof ValidationError) { throw error; } throw new EncryptionError( `Failed to check existing secrets: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } } /** * Retrieves the structured secret data containing encryptedDEK and kmsPath * @param secretName - Name of the secret to retrieve * @returns Promise resolving to the structured secret data with version path * @throws {ValidationError} When secret name is missing * @throws {EncryptionError} When secret retrieval fails */ async getStructuredSecret(secretName) { var _a; try { if (!secretName) { throw new ValidationError( "Secret name is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const [version] = await this.secretManager.accessSecretVersion({ name: `${secretName}/versions/latest` }); const secretPayload = (_a = version.payload) == null ? void 0 : _a.data; if (!secretPayload) { throw new EncryptionError( `No payload data found in secret for ${secretName}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } const payloadBuffer = Buffer.from(secretPayload); try { const payloadString = payloadBuffer.toString("utf8"); const secretData = JSON.parse(payloadString); if (secretData.encryptedDEK && secretData.kmsPath) { return { encryptedDEK: Buffer.from(secretData.encryptedDEK, "base64"), kmsPath: secretData.kmsPath, secretNamePath: version.name || `${secretName}/versions/latest` }; } } catch (parseError) { console.log("\u{1F680} ~ SecretManagerService ~ getStructuredSecret ~ Legacy format detected, raw buffer:", payloadBuffer.length, "bytes"); } return { encryptedDEK: payloadBuffer, kmsPath: "", // Legacy secrets don't have kmsPath stored secretNamePath: version.name || `${secretName}/versions/latest` }; } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to access structured secret: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } } /** * Creates a new secret and adds the encrypted DEK as a version * @param secretId - Unique identifier for the secret * @param secretData - Data to be stored in the secret * @returns Promise resolving to the created secret version * @throws {ValidationError} When required parameters are missing * @throws {EncryptionError} When secret creation fails */ async createSecret(secretId, secretData) { try { if (!secretId) { throw new ValidationError( "Secret ID is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } if (!secretData.encryptedDEK) { throw new ValidationError( "Encrypted DEK is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } if (!secretData.kmsPath) { throw new ValidationError( "KMS path is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const parent = `projects/${this.projectId}`; const [secret] = await this.secretManager.createSecret({ parent, secretId, secret: { replication: { automatic: {} } } }); if (!secret.name) { throw new EncryptionError( "Failed to create secret: No name returned", ErrorCodes.ENCRYPTION.CREATION_ERROR ); } const structuredSecretData = { encryptedDEK: secretData.encryptedDEK.toString("base64"), kmsPath: secretData.kmsPath }; const [version] = await this.secretManager.addSecretVersion({ parent: secret.name, payload: { data: Buffer.from(JSON.stringify(structuredSecretData)) } }); if (!version.name) { throw new EncryptionError( "Failed to create secret version: No name returned", ErrorCodes.ENCRYPTION.CREATION_ERROR ); } return { secretName: secret.name, secretNamePath: version.name }; } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to create secret: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.CREATION_ERROR ); } } /** * Retrieves the latest version of a secret (backward compatible - returns only encryptedDEK) * @param secretNamePath - Path to the secret version to retrieve * @returns Promise resolving to the encryptedDEK as a Buffer * @throws {ValidationError} When secret name is missing * @throws {EncryptionError} When secret retrieval fails */ async getSecret(secretNamePath) { var _a; try { if (!secretNamePath) { throw new ValidationError( "Secret name is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const [version] = await this.secretManager.accessSecretVersion({ name: secretNamePath }); const secretPayload = (_a = version.payload) == null ? void 0 : _a.data; if (!secretPayload) { throw new EncryptionError( `No payload data found in secret version for ${secretNamePath}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } const payloadBuffer = Buffer.from(secretPayload); try { const payloadString = payloadBuffer.toString("utf8"); const secretData = JSON.parse(payloadString); if (secretData.encryptedDEK && typeof secretData.encryptedDEK === "string") { return Buffer.from(secretData.encryptedDEK, "base64"); } } catch (parseError) { } return payloadBuffer; } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to access secret: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } } }; var secretManagerService_default = new SecretManagerService(); // src/utils/createHash.ts var import_crypto2 = __toESM(require("crypto")); var createHash = (data) => { return import_crypto2.default.createHash("sha256").update(data).digest("hex"); }; var createHash_default = createHash; // src/utils/configUtils.ts var import_fs = __toESM(require("fs")); var import_path3 = __toESM(require("path")); var configCache = null; var getEncryptionConfig = () => { if (configCache) { return configCache; } try { const configPath = process.env.CONFIG_PATH || import_path3.default.join(process.cwd(), "config/encryption.json"); const config = JSON.parse(import_fs.default.readFileSync(configPath, "utf8")); configCache = config; return config; } catch (error) { throw new ConfigurationError( `Failed to read encryption configuration: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.CONFIGURATION.MISSING_CONFIG ); } }; var getModelConfig = (modelName) => { const config = getEncryptionConfig(); const modelConfig = config[modelName]; if (!modelConfig) { throw new ConfigurationError( `Encryption configuration not found for model ${modelName}`, ErrorCodes.CONFIGURATION.MISSING_CONFIG ); } return modelConfig; }; // src/utils/encryptionUtils.ts var generateDEK = (length = 32) => { return import_crypto3.default.randomBytes(length); }; var encryptField = async (fieldName, value, dek) => { if (!value) return null; const iv = import_crypto3.default.randomBytes(16); const cipher = import_crypto3.default.createCipheriv("aes-256-gcm", dek, iv); let encrypted = cipher.update(value, "utf8", "hex"); encrypted += cipher.final("hex"); const authTag = cipher.getAuthTag(); return { // [fieldName]: encrypted, [`${fieldName}_encrypted`]: encrypted, [`${fieldName}_iv`]: iv.toString("hex"), [`${fieldName}_auth_tag`]: authTag.toString("hex") }; }; var decryptField = async (fieldName, data, dek) => { const encryptedFieldName = `${fieldName}_encrypted`; if (!data[encryptedFieldName]) return null; try { const decipher = import_crypto3.default.createDecipheriv( "aes-256-gcm", dek, Buffer.from(data[`${fieldName}_iv`], "hex") ); decipher.setAuthTag(Buffer.from(data[`${fieldName}_auth_tag`], "hex")); let decrypted = decipher.update(data[encryptedFieldName], "hex", "utf8"); decrypted += decipher.final("utf8"); return decrypted; } catch (error) { console.error(`Error decrypting field ${fieldName}:`, error); return data[fieldName] || null; } }; var handleFieldEncryption = async (modelName, data, dek) => { try { const modelConfig = getModelConfig(modelName); const encryptedData = { ...data }; for (const [fieldName, fieldConfig] of Object.entries(modelConfig)) { if (fieldConfig.shouldEncrypt && data[fieldName]) { try { if (fieldConfig.isObject) { if (typeof data[fieldName] === "object") { const nestedEncrypted = await handleFieldEncryption( fieldName, data[fieldName], dek ); encryptedData[fieldName] = nestedEncrypted; } } else if (fieldConfig.isArrayOfObjects) { if (Array.isArray(data[fieldName])) { encryptedData[fieldName] = await Promise.all( data[fieldName].map(async (item) => { return await handleFieldEncryption( fieldName, item, dek ); }) ); } } else { const fieldData = await encryptField(fieldName, data[fieldName], dek); if (fieldConfig.shouldHash) { const hash = createHash_default(data[fieldName]); encryptedData[`${fieldName}_hash`] = hash; } if (fieldData) { Object.assign(encryptedData, fieldData); } } } catch (error) { throw new EncryptionError( `Failed to encrypt field ${fieldName}: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.FIELD_ENCRYPTION_ERROR ); } } } return encryptedData; } catch (error) { if (error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to handle field encryption: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.FIELD_ENCRYPTION_ERROR ); } }; var createNewEncryption = async (clientName) => { try { const existingSecretName = await secretManagerService_default.getExistingSecretForClient(clientName); if (existingSecretName) { console.log("Secret already exists for client:", clientName, "- Retrieving existing secret"); const existingSecretData = await secretManagerService_default.getStructuredSecret(existingSecretName); if (!existingSecretData.kmsPath && existingSecretData.kmsPath.startsWith("projects/")) { const secretId2 = existingSecretName.split("/").pop() || `secret-${clientName}`; return { kmsPath: existingSecretData.kmsPath, secretId: secretId2, secretNamePath: existingSecretData.secretNamePath, encryptedDEK: existingSecretData.encryptedDEK }; } else { console.log("Legacy secret detected for client:", clientName, "- Missing kmsPath, checking KMS service"); const existingKMSPath = await kmsService_default.getExistingKMSPath(clientName); if (existingKMSPath) { const secretId2 = existingSecretName.split("/").pop() || `secret-${clientName}`; console.log("Found existing KMS key for legacy secret - KMS Path:", existingKMSPath); return { kmsPath: existingKMSPath, secretId: secretId2, secretNamePath: existingSecretData.secretNamePath, encryptedDEK: existingSecretData.encryptedDEK }; } else { console.log("No existing KMS key found for client:", clientName, "- Will create new secret"); } } } console.log("No existing secret found for client:", clientName, "- Creating new secret"); const dek = generateDEK(); const { encryptedDEK, kmsPath } = await kmsService_default.encryptDEK(dek, clientName); const secretId = kmsService_default.createSecretKeyWithEnv(clientName); const { secretNamePath } = await secretManagerService_default.createSecret(secretId, { encryptedDEK, kmsPath }); return { kmsPath, secretId, secretNamePath, encryptedDEK }; } catch (error) { throw new EncryptionError( `Failed to create new encryption: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.CREATION_ERROR ); } }; // src/services/encryptionService.ts var import_fs2 = __toESM(require("fs")); var import_path4 = __toESM(require("path")); var EncryptionService = class { constructor() { const configPath = process.env.CONFIG_PATH || import_path4.default.join(process.cwd(), "config/encryption.json"); const keyMaping = import_path4.default.join(process.cwd(), "config/keyMaping.json"); this.config = JSON.parse(import_fs2.default.readFileSync(configPath, "utf8")); this.keyMaping = JSON.parse(import_fs2.default.readFileSync(keyMaping, "utf8")); } /** * Encrypts an object's fields based on model configuration * @param params - Object containing required parameters for encryption * @returns Object containing encrypted data and key metadata * @throws {ConfigurationError} When encryption configuration is missing * @throws {EncryptionError} When encryption operations fail * @throws {ValidationError} When required parameters are missing */ async encryptObject({ modelName, data, entityKeyDetailsResult }) { try { if (!modelName) { throw new ValidationError( "Model name is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } if (!data) { throw new ValidationError( "Data to encrypt is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } if (!(entityKeyDetailsResult == null ? void 0 : entityKeyDetailsResult.kmsPath)) { throw new ValidationError( "Entity key details are required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } try { const { kmsPath, secretId, secretNamePath, encryptedDEK } = entityKeyDetailsResult; if (!kmsPath || !secretId || !secretNamePath) { throw new ValidationError( "Missing required key details", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const { dek, encryptedDEK: encryptedDEKFromSecret } = await this.resolveDEKFromEntityKeyDetails({ kmsPath, secretId, secretNamePath, encryptedDEK }); const encryptedData = await handleFieldEncryption(modelName, data, dek); return { encryptedData, encryptedDEK: encryptedDEKFromSecret }; } catch (error) { throw new EncryptionError( `Failed to use existing key details: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.KEY_DETAILS_ERROR ); } } catch (error) { if (error instanceof EncryptionError || error instanceof ConfigurationError || error instanceof ValidationError) { throw error; } throw new EncryptionError( `Unexpected error during encryption: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.UNKNOWN_ERROR ); } } /** * Resolves DEK from entity key details, handling both cached and non-cached scenarios * @param entityKeyDetails - Entity's key details containing KMS metadata * @returns Object containing decrypted DEK and key metadata * @throws {ValidationError} When required key details are missing * @throws {EncryptionError} When DEK resolution fails */ async resolveDEKFromEntityKeyDetails(entityKeyDetails) { try { if (!entityKeyDetails.secretId || !entityKeyDetails.kmsPath || !entityKeyDetails.secretNamePath) { throw new ValidationError( "Missing required key details", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } let encryptedDEK = entityKeyDetails.encryptedDEK; if (!encryptedDEK) { try { encryptedDEK = await secretManagerService_default.getSecret( entityKeyDetails.secretNamePath ); } catch (error) { throw new EncryptionError( `Failed to retrieve secret: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.SECRET_RETRIEVAL_ERROR ); } } try { const dek = await kmsService_default.decryptDEK({ encryptedDEKData: encryptedDEK, kmsPath: entityKeyDetails.kmsPath }); return { dek, encryptedDEK }; } catch (error) { throw new EncryptionError( `Failed to decrypt DEK: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.DEK_DECRYPTION_ERROR ); } } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } throw new EncryptionError( `Failed to resolve DEK: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.DEK_RESOLUTION_ERROR ); } } /** * Decrypts an object's fields based on model configuration * @param params - Object containing required parameters for decryption * @returns Object containing decrypted data and optional encrypted DEK * @throws {ConfigurationError} When encryption configuration is missing * @throws {EncryptionError} When decryption operations fail * @throws {ValidationError} When required parameters are missing */ async decryptObject({ modelName, data, entityKeyDetailsResult }) { var _a; try { if (!modelName) { throw new ValidationError( "Model name is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } if (!data) { throw new ValidationError( "Data to decrypt is required", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const modelConfig = this.config[modelName]; if (!modelConfig) { throw new ConfigurationError( `Encryption configuration not found for model ${modelName}`, ErrorCodes.CONFIGURATION.MISSING_CONFIG ); } const decryptedData = { ...data }; let encryptedDEKFromSecret; if (entityKeyDetailsResult == null ? void 0 : entityKeyDetailsResult.kmsPath) { try { const { kmsPath } = entityKeyDetailsResult; if (!kmsPath) { throw new ValidationError( "Missing required key details", ErrorCodes.VALIDATION.MISSING_REQUIRED_FIELD ); } const { dek, encryptedDEK } = await this.resolveDEKFromEntityKeyDetails(entityKeyDetailsResult); encryptedDEKFromSecret = encryptedDEK; for (const [fieldName, fieldConfig] of Object.entries(modelConfig)) { if (fieldConfig.shouldDecrypt) { const encryptedFieldName = `${fieldName}_encrypted`; if (fieldConfig.isObject && typeof data[fieldName] === "object") { const dataValues = ((_a = data[fieldName]) == null ? void 0 : _a.dataValues) || data[fieldName]; const nestedDecrypted = await this.decryptObject({ modelName: fieldName, data: dataValues, entityKeyDetailsResult }); decryptedData[fieldName] = nestedDecrypted.decryptedData; } else if (fieldConfig.isArrayOfObjects && Array.isArray(data[fieldName])) { decryptedData[fieldName] = await Promise.all( data[fieldName].map(async (item) => { let modalNameFromKeyMaping; if (this.keyMaping[fieldName]) { modalNameFromKeyMaping = this.keyMaping[fieldName]; } else { modalNameFromKeyMaping = fieldName; } const decrypted = await this.decryptObject({ modelName: modalNameFromKeyMaping, data: (item == null ? void 0 : item.dataValues) || item, entityKeyDetailsResult }); return decrypted.decryptedData; }) ); } else if (data[encryptedFieldName]) { try { const decryptedValue = await decryptField( fieldName, data, dek ); if (decryptedValue !== null) { decryptedData[fieldName] = decryptedValue; decryptedData[encryptedFieldName] = decryptedValue; delete decryptedData[`${fieldName}_iv`]; delete decryptedData[`${fieldName}_auth_tag`]; } else { console.log(`******************Field not able to decrypt sending original value**********************`); decryptedData[encryptedFieldName] = decryptedValue; delete decryptedData[`${fieldName}_iv`]; delete decryptedData[`${fieldName}_auth_tag`]; } } catch (error) { console.warn(`Failed to decrypt field ${fieldName}:`, error); decryptedData[encryptedFieldName] = data[fieldName]; } } } } } catch (error) { if (error instanceof ValidationError || error instanceof EncryptionError) { throw error; } } } return { decryptedData, encryptedDEK: encryptedDEKFromSecret }; } catch (error) { if (error instanceof EncryptionError || error instanceof ConfigurationError || error instanceof ValidationError) { throw error; } throw new EncryptionError( `Unexpected error during decryption: ${error instanceof Error ? error.message : "Unknown error"}`, ErrorCodes.ENCRYPTION.UNKNOWN_ERROR ); } } }; var encryptionService_default = new EncryptionService(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ConfigurationError, EncryptionError, EncryptionService, ErrorCodes, ValidationError, createHash, createNewEncryption, decryptField, encryptField, generateDEK, handleFieldEncryption, kmsService, secretManagerService });