From b085368ef37ad34db1e2d489a602fb4f70d62e10 Mon Sep 17 00:00:00 2001 From: mahmishr Date: Fri, 20 Jun 2025 10:30:57 +0530 Subject: [PATCH 1/9] pgp mtls --- src/api/BatchUploadWithMTLSApi.js | 122 ++++++++++++++++ .../PGP/BatchUpload/BatchUploadUtility.js | 118 +++++++++++++++ .../BatchUpload/MutualAuthUploadUtility.js | 136 ++++++++++++++++++ .../PGP/BatchUpload/PgpEncryptionUtility.js | 39 +++++ 4 files changed, 415 insertions(+) create mode 100644 src/api/BatchUploadWithMTLSApi.js create mode 100644 src/utilities/PGP/BatchUpload/BatchUploadUtility.js create mode 100644 src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js create mode 100644 src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js diff --git a/src/api/BatchUploadWithMTLSApi.js b/src/api/BatchUploadWithMTLSApi.js new file mode 100644 index 00000000..d7e1cf0e --- /dev/null +++ b/src/api/BatchUploadWithMTLSApi.js @@ -0,0 +1,122 @@ +const path = require('path'); +const fs = require('fs'); +const { handlePGPEncrypt } = require('../utilities/PGP/BatchUpload/PgpEncryptionUtility'); +const { + handleUploadOperationUsingP12orPfx, + handleUploadOperationUsingPrivateKeyAndCerts +} = require('../utilities/PGP/BatchUpload/MutualAuthUploadUtility'); +const BatchUploadUtility = require('../utilities/PGP/BatchUpload/BatchUploadUtility'); + +/** + * BatchUploadWithMTLSApi + * Class for uploading batch files to CyberSource using mutual TLS authentication. + * Supports PKCS#12 client certificates, and direct private key/certificate objects. + * Handles PGP encryption of files before upload. + */ +class BatchUploadWithMTLSApi { + constructor(logger = console) { + this.logger = logger; + } + + uploadBatchAPIWithP12(opts, callback) { + try { + const { + inputFilePath, + environmentHostname, + publicKeyFilePath, + clientCertP12FilePath, + clientCertP12Password, + serverTrustCertPath, + rejectUnauthorizedFlag = true + } = opts; + if (rejectUnauthorizedFlag === false) { + this.logger.warn('rejectUnauthorized is set to false. This is NOT recommended for production environments.'); + } + this.logger.info('Starting batch upload with p12/pfx for given file'); + const endpoint = '/pts/v1/transaction-batch-upload'; + const endpointUrl = BatchUploadUtility.getEndpointUrl(environmentHostname, endpoint); + BatchUploadUtility.validateBatchApiP12Inputs( + inputFilePath, environmentHostname, publicKeyFilePath, clientCertP12FilePath, serverTrustCertPath + ); + + handlePGPEncrypt(inputFilePath, publicKeyFilePath) + .then(encryptedBuffer => { + const uploadFileName = path.basename(inputFilePath) + '.pgp'; + const clientCertP12 = fs.readFileSync(clientCertP12FilePath); + const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; + return handleUploadOperationUsingP12orPfx( + encryptedBuffer, + endpointUrl, + environmentHostname, + uploadFileName, + clientCertP12, + clientCertP12Password, + serverTrustCert, + rejectUnauthorizedFlag + ); + }) + .then(result => callback(null, result)) + .catch(error => { + this.logger.error(error); + callback(error); + }); + } catch (e) { + this.logger.error('Exception in Batch Upload API', e); + callback(e); + } + } + + uploadBatchAPIWithKeys(opts, callback) { + try { + const { + inputFilePath, + environmentHostname, + publicKeyFilePath, + clientPrivateKeyFilePath, + clientCertFilePath, + serverTrustCertPath, + clientKeyPassword, + rejectUnauthorizedFlag = true + + } = opts; + if (rejectUnauthorizedFlag === false) { + this.logger.warn('rejectUnauthorized is set to false. This is NOT recommended for production environments.'); + } + this.logger.info('Starting batch upload with client private key and certs for given file'); + const endpoint = '/pts/v1/transaction-batch-upload'; + const endpointUrl = BatchUploadUtility.getEndpointUrl(environmentHostname, endpoint); + BatchUploadUtility.validateBatchApiKeysInputs( + inputFilePath, environmentHostname, publicKeyFilePath, clientPrivateKeyFilePath, clientCertFilePath, serverTrustCertPath + ); + + handlePGPEncrypt(inputFilePath, publicKeyFilePath) + .then(encryptedBuffer => { + const uploadFileName = path.basename(inputFilePath) + '.pgp'; + const clientPrivateKey = fs.readFileSync(clientPrivateKeyFilePath); + const clientCert = fs.readFileSync(clientCertFilePath); + const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; + return handleUploadOperationUsingPrivateKeyAndCerts( + encryptedBuffer, + endpointUrl, + environmentHostname, + uploadFileName, + clientPrivateKey, + clientCert, + serverTrustCert, + clientKeyPassword, + rejectUnauthorizedFlag + ); + }) + .then(result => callback(null, result)) + .catch(error => { + this.logger.error(error); + callback(error); + }); + } catch (e) { + this.logger.error('Exception in Batch Upload API', e); + callback(e); + } + } +} + +module.exports = BatchUploadWithMTLSApi; \ No newline at end of file diff --git a/src/utilities/PGP/BatchUpload/BatchUploadUtility.js b/src/utilities/PGP/BatchUpload/BatchUploadUtility.js new file mode 100644 index 00000000..d0260113 --- /dev/null +++ b/src/utilities/PGP/BatchUpload/BatchUploadUtility.js @@ -0,0 +1,118 @@ +const fs = require('fs'); +const path = require('path'); + +const MAX_FILE_SIZE_BYTES = 75 * 1024 * 1024; // 75MB + +class BatchUploadUtility { + /** + * Constructs the full endpoint URL for the given environment hostname and endpoint path. + * @param {string} environmentHostname - The environment hostname (with or without protocol prefix). + * @param {string} endpoint - The endpoint path to append. + * @returns {string} The full endpoint URL. + */ + static getEndpointUrl(environmentHostname, endpoint) { + const URL_PREFIX = 'https://'; + let baseUrl; + if (environmentHostname.trim().toLowerCase().startsWith(URL_PREFIX)) { + baseUrl = environmentHostname.trim(); + } else { + baseUrl = URL_PREFIX + environmentHostname.trim(); + } + return baseUrl + endpoint; + } + + /** + * Validates the input parameters for batch API using P12 client certificate. + * @param {string} inputFile - Path to the input CSV file for batch upload. + * @param {string} environmentHostname + * @param {string} pgpEncryptionCertPath + * @param {string} clientCertP12FilePath + * @param {string} serverTrustCertPath + */ + static validateBatchApiP12Inputs(inputFile, environmentHostname, pgpEncryptionCertPath, clientCertP12FilePath, serverTrustCertPath) { + this.validateInputFile(inputFile); + if (!environmentHostname || !environmentHostname.trim()) { + throw new Error('Environment Host Name for Batch Upload API cannot be null or empty.'); + } + this.validatePathAndFile(pgpEncryptionCertPath, 'PGP Encryption Cert Path'); + this.validatePathAndFile(clientCertP12FilePath, 'Client Cert P12 File Path'); + // serverTrustCertPath is optional, but if provided, validate + if (serverTrustCertPath && serverTrustCertPath.trim()) { + this.validatePathAndFile(serverTrustCertPath, 'Server Trust Cert Path'); + } + } + + /** + * Validates the input parameters for batch API using direct key and certificate file paths. + * @param {string} inputFile - Path to the input CSV file for batch upload. + * @param {string} environmentHostname + * @param {string} pgpPublicKeyPath - Path to the PGP public key file (.asc). + * @param {string} clientPrivateKeyPath - Path to the client private key file (PEM). + * @param {string} clientCertPath - Path to the client X509 certificate file (PEM). + * @param {string} serverTrustCertPath - Path to the server trust X509 certificate file (PEM, optional). + */ + static validateBatchApiKeysInputs(inputFile, environmentHostname, pgpPublicKeyPath, clientPrivateKeyPath, clientCertPath, serverTrustCertPath) { + this.validateInputFile(inputFile); + if (!environmentHostname || !environmentHostname.trim()) { + throw new Error('Environment Host Name for Batch Upload API cannot be null or empty.'); + } + this.validatePathAndFile(pgpPublicKeyPath, 'PGP Public Key Path'); + this.validatePathAndFile(clientPrivateKeyPath, 'Client Private Key Path'); + this.validatePathAndFile(clientCertPath, 'Client Certificate Path'); + // serverTrustCertPath is optional, but if provided, validate + if (serverTrustCertPath && serverTrustCertPath.trim()) { + this.validatePathAndFile(serverTrustCertPath, 'Server Trust Certificate Path'); + } + } + + /** + * Validates the input file for batch upload. + * Checks for existence, file type (CSV), and maximum file size (75MB). + * @param {string} inputFile - Path to the input file to validate. + */ + static validateInputFile(inputFile) { + if (!inputFile || !fs.existsSync(inputFile) || !fs.statSync(inputFile).isFile()) { + throw new Error(`Input file is invalid or does not exist: ${inputFile}`); + } + // Only CSV files are allowed for batch API + if (!inputFile.toLowerCase().endsWith('.csv')) { + throw new Error(`Only CSV file type is allowed: ${path.basename(inputFile)}`); + } + // Max file size allowed is 75MB + const fileSize = fs.statSync(inputFile).size; + if (fileSize > MAX_FILE_SIZE_BYTES) { + throw new Error(`Input file size exceeds the maximum allowed size of 75MB: ${fileSize}`); + } + } + + /** + * Validates that the given file path exists and is not empty. + * @param {string} filePath - The file path to validate. + * @param {string} pathType - A description of the path type (e.g., "Input file"). + */ + static validatePathAndFile(filePath, pathType) { + if (!filePath || !filePath.trim()) { + throw new Error(`${pathType} path cannot be null or empty`); + } + + // Normalize Windows-style paths that start with a slash before the drive letter + let normalizedPath = filePath; + if (path.sep === '\\' && normalizedPath.match(/^\/[A-Za-z]:.*/)) { + normalizedPath = normalizedPath.substring(1); + } + + if (!fs.existsSync(normalizedPath)) { + throw new Error(`${pathType} does not exist: ${normalizedPath}`); + } + if (!fs.statSync(normalizedPath).isFile()) { + throw new Error(`${pathType} does not have valid file: ${normalizedPath}`); + } + try { + fs.accessSync(normalizedPath, fs.constants.R_OK); + } catch (err) { + throw new Error(`${pathType} is not readable: ${normalizedPath}`); + } + } +} + +module.exports = BatchUploadUtility; \ No newline at end of file diff --git a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js new file mode 100644 index 00000000..7673c9ed --- /dev/null +++ b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js @@ -0,0 +1,136 @@ +const axios = require('axios'); +const fs = require('fs'); +const https = require('https'); +const tls = require('tls'); +const FormData = require('form-data'); +const { v4: uuidv4 } = require('uuid'); + +/** + * Uploads an encrypted PGP file using mTLS with PKCS#12 (.p12/.pfx) client certificate. + * @param {Buffer} encryptedPgpBytes - The encrypted PGP file content as Buffer. + * @param {string} environmentHostname - The environment hostname. + * @param {string} fileName - The name of the file to be uploaded. + * @param {Buffer} clientCertP12 - The PKCS#12 client certificate as Buffer. + * @param {string} clientCertP12Password - Password for the PKCS#12 client certificate. + * @param {Buffer} serverTrustCert - The server trust certificate as Buffer. + * @returns {Promise} + */ +function handleUploadOperationUsingP12orPfx( + encryptedPgpBytes, + endpointUrl, + environmentHostname, + fileName, + clientCertP12, + clientCertP12Password, + serverTrustCert, + rejectUnauthorizedFlag +) { + const form = new FormData(); + form.append('file', encryptedPgpBytes, { filename: fileName }); + + const defaultCAs = tls.rootCertificates.slice(); + + if (serverTrustCert && serverTrustCert.length > 0) { + defaultCAs.push(serverTrustCert.toString()); + } + + const correlationId = uuidv4(); + + const httpsAgent = new https.Agent({ + pfx: clientCertP12, + passphrase: clientCertP12Password, + ca: defaultCAs, + rejectUnauthorized: rejectUnauthorizedFlag, + servername: environmentHostname + }); + + return axios.post(endpointUrl, form, { + httpsAgent, + headers: { + ...form.getHeaders(), + 'v-c-correlation-id': correlationId + } + }).then(response => { + if (response.status === 201) { + return { + status: response.status, + statusText: response.statusText, + data: response.data, + message: "File uploaded successfully" + }; + } throw { + response + }; + }).catch(error => { + return Promise.reject(normalizeError(error)); + }); +} + +/** + * Uploads an encrypted PGP file using mTLS with client key/cert as PEM Buffers/strings. + * @param {Buffer} encryptedPgpBytes - The encrypted PGP file content as Buffer. + * @param {string} environmentHostname - The environment hostname. + * @param {string} fileName - The name of the file to be uploaded. + * @param {Buffer|string} clientPrivateKey - The client's private key (PEM string or Buffer). + * @param {Buffer|string} clientCert - The client's X509 certificate (PEM string or Buffer). + * @param {Buffer|string} serverTrustCert - The server's trust X509 certificate (PEM string or Buffer). + * @param {string} [clientKeyPassword] - Password for the private key, if encrypted. + * @returns {Promise} + */ +function handleUploadOperationUsingPrivateKeyAndCerts( + encryptedPgpBytes, + endpointUrl, + environmentHostname, + fileName, + clientPrivateKey, + clientCert, + serverTrustCert, + clientKeyPassword, + rejectUnauthorizedFlag +) { + const form = new FormData(); + form.append('file', encryptedPgpBytes, { filename: fileName }); + + const defaultCAs = tls.rootCertificates.slice(); + + if (serverTrustCert && serverTrustCert.length > 0) { + defaultCAs.push(serverTrustCert.toString()); + } + const httpsAgent = new https.Agent({ + key: clientPrivateKey, + cert: clientCert, + ca: defaultCAs, + passphrase: clientKeyPassword, + rejectUnauthorized: rejectUnauthorizedFlag, + servername: environmentHostname + }); + + return axios.post(endpointUrl, form, { + httpsAgent, + headers: form.getHeaders() + }).then(response => { + if (response.status === 201) { + return { + status: response.status, + statusText: response.statusText, + data: response.data, + message: "File uploaded successfully" + }; + } throw { + response + }; + }).catch(error => { + return Promise.reject(normalizeError(error)); + }); +} + +function normalizeError(error) { + return { + message: `File upload failed: ${error.message}` + }; +} + +module.exports = { + handleUploadOperationUsingP12orPfx, + handleUploadOperationUsingPrivateKeyAndCerts +}; \ No newline at end of file diff --git a/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js b/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js new file mode 100644 index 00000000..3a255d72 --- /dev/null +++ b/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const openpgp = require('openpgp'); + +/** + * Encrypts a file using a PGP public key (.asc). + * @param {string} inputFilePath - Path to the file to encrypt. + * @param {string} publicKeyPath - Path to the public .asc key or armored key string. + * @returns {Promise} - The encrypted file as a Buffer. + */ +function handlePGPEncrypt(inputFilePath, publicKeyPath) { + return new Promise((resolve, reject) => { + if (!inputFilePath || !publicKeyPath) { + return reject(new Error('Missing required options for encrypt operation')); + } + let publicKeyArmored, fileData; + try { + publicKeyArmored = fs.readFileSync(publicKeyPath, 'utf8'); + fileData = fs.readFileSync(inputFilePath); + } catch (err) { + return reject(err); + } + openpgp.readKey({ armoredKey: publicKeyArmored }) + .then(publicKey => { + return openpgp.createMessage({ binary: fileData }) + .then(message => { + return openpgp.encrypt({ + message, + encryptionKeys: publicKey, + config: { preferredCompressionAlgorithm: openpgp.enums.compression.zip }, + format: 'binary' + }); + }); + }) + .then(encrypted => resolve(Buffer.from(encrypted))) + .catch(reject); + }); +} + +module.exports = { handlePGPEncrypt }; \ No newline at end of file From 2df31aac8e48db04fa4228505addf3c0bc99e01b Mon Sep 17 00:00:00 2001 From: mahmishr Date: Fri, 20 Jun 2025 11:17:02 +0530 Subject: [PATCH 2/9] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1c000e84..3b860067 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "jwt-simple": "^0.5.6", "memory-cache": "^0.2.0", "node-forge": ">=1.0.0", + "openpgp": "^5.11.3", "promise": "^8.3.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", From bb0fe83e4b44fdd9f3c3300603a49e19485cf008 Mon Sep 17 00:00:00 2001 From: mahmishr Date: Fri, 20 Jun 2025 11:22:31 +0530 Subject: [PATCH 3/9] Update cybersource_node_sdk_gen.bat --- generator/cybersource_node_sdk_gen.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/generator/cybersource_node_sdk_gen.bat b/generator/cybersource_node_sdk_gen.bat index d3cc34bc..c77f4233 100644 --- a/generator/cybersource_node_sdk_gen.bat +++ b/generator/cybersource_node_sdk_gen.bat @@ -26,6 +26,7 @@ git checkout ..\src\model\CreateAccessTokenRequest.js git checkout ..\src\model\BadRequestError.js git checkout ..\src\model\ResourceNotFoundError.js git checkout ..\src\model\UnauthorizedClientError.js +git checkout ..\src\api\BatchUploadWithMTLSApi.js git checkout ..\docs\OAuthApi.md git checkout ..\docs\AccessTokenResponse.md From 0d1d10719c29b12dcadbb8dca23c89f8d317b9ae Mon Sep 17 00:00:00 2001 From: mahmishr Date: Fri, 20 Jun 2025 13:12:51 +0530 Subject: [PATCH 4/9] code refactored --- src/api/BatchUploadWithMTLSApi.js | 7 ++-- .../BatchUpload/MutualAuthUploadUtility.js | 40 ++++++++++--------- .../PGP/BatchUpload/PgpEncryptionUtility.js | 3 -- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/api/BatchUploadWithMTLSApi.js b/src/api/BatchUploadWithMTLSApi.js index d7e1cf0e..5d5dd60d 100644 --- a/src/api/BatchUploadWithMTLSApi.js +++ b/src/api/BatchUploadWithMTLSApi.js @@ -10,7 +10,7 @@ const BatchUploadUtility = require('../utilities/PGP/BatchUpload/BatchUploadUtil /** * BatchUploadWithMTLSApi * Class for uploading batch files to CyberSource using mutual TLS authentication. - * Supports PKCS#12 client certificates, and direct private key/certificate objects. + * Supports PKCS#12 client certificates, and direct private key/certificate paths. * Handles PGP encryption of files before upload. */ class BatchUploadWithMTLSApi { @@ -30,7 +30,7 @@ class BatchUploadWithMTLSApi { rejectUnauthorizedFlag = true } = opts; if (rejectUnauthorizedFlag === false) { - this.logger.warn('rejectUnauthorized is set to false. This is NOT recommended for production environments.'); + this.logger.warn('rejectUnauthorized is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); } this.logger.info('Starting batch upload with p12/pfx for given file'); const endpoint = '/pts/v1/transaction-batch-upload'; @@ -42,6 +42,7 @@ class BatchUploadWithMTLSApi { handlePGPEncrypt(inputFilePath, publicKeyFilePath) .then(encryptedBuffer => { const uploadFileName = path.basename(inputFilePath) + '.pgp'; + console.log('Encrypted file name:', uploadFileName); const clientCertP12 = fs.readFileSync(clientCertP12FilePath); const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; return handleUploadOperationUsingP12orPfx( @@ -80,7 +81,7 @@ class BatchUploadWithMTLSApi { } = opts; if (rejectUnauthorizedFlag === false) { - this.logger.warn('rejectUnauthorized is set to false. This is NOT recommended for production environments.'); + this.logger.warn('rejectUnauthorized is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); } this.logger.info('Starting batch upload with client private key and certs for given file'); const endpoint = '/pts/v1/transaction-batch-upload'; diff --git a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js index 7673c9ed..7d204de9 100644 --- a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js +++ b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js @@ -29,7 +29,7 @@ function handleUploadOperationUsingP12orPfx( form.append('file', encryptedPgpBytes, { filename: fileName }); const defaultCAs = tls.rootCertificates.slice(); - + //Assuming serverTrustCert is a PEM encoded certificate if (serverTrustCert && serverTrustCert.length > 0) { defaultCAs.push(serverTrustCert.toString()); } @@ -52,12 +52,7 @@ function handleUploadOperationUsingP12orPfx( } }).then(response => { if (response.status === 201) { - return { - status: response.status, - statusText: response.statusText, - data: response.data, - message: "File uploaded successfully" - }; + return normalizeResponse(response); } throw { response }; @@ -71,9 +66,9 @@ function handleUploadOperationUsingP12orPfx( * @param {Buffer} encryptedPgpBytes - The encrypted PGP file content as Buffer. * @param {string} environmentHostname - The environment hostname. * @param {string} fileName - The name of the file to be uploaded. - * @param {Buffer|string} clientPrivateKey - The client's private key (PEM string or Buffer). - * @param {Buffer|string} clientCert - The client's X509 certificate (PEM string or Buffer). - * @param {Buffer|string} serverTrustCert - The server's trust X509 certificate (PEM string or Buffer). + * @param {Buffer|string} clientPrivateKey - The client's private key (Buffer). + * @param {Buffer|string} clientCert - The client's certificate (Buffer). + * @param {Buffer|string} serverTrustCert - The server's trust certificate (Buffer). * @param {string} [clientKeyPassword] - Password for the private key, if encrypted. * @returns {Promise} */ @@ -91,8 +86,10 @@ function handleUploadOperationUsingPrivateKeyAndCerts( const form = new FormData(); form.append('file', encryptedPgpBytes, { filename: fileName }); - const defaultCAs = tls.rootCertificates.slice(); + const correlationId = uuidv4(); + const defaultCAs = tls.rootCertificates.slice(); + //Assuming serverTrustCert is a PEM encoded certificate if (serverTrustCert && serverTrustCert.length > 0) { defaultCAs.push(serverTrustCert.toString()); } @@ -107,15 +104,13 @@ function handleUploadOperationUsingPrivateKeyAndCerts( return axios.post(endpointUrl, form, { httpsAgent, - headers: form.getHeaders() + headers: { + ...form.getHeaders(), + 'v-c-correlation-id': correlationId + } }).then(response => { if (response.status === 201) { - return { - status: response.status, - statusText: response.statusText, - data: response.data, - message: "File uploaded successfully" - }; + return normalizeResponse(response); } throw { response }; @@ -124,6 +119,15 @@ function handleUploadOperationUsingPrivateKeyAndCerts( }); } +function normalizeResponse(response) { + return { + status: response.status, + statusText: response.statusText, + data: response.data, + message: "File uploaded successfully" + }; +} + function normalizeError(error) { return { message: `File upload failed: ${error.message}` diff --git a/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js b/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js index 3a255d72..b8ff335b 100644 --- a/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js +++ b/src/utilities/PGP/BatchUpload/PgpEncryptionUtility.js @@ -9,9 +9,6 @@ const openpgp = require('openpgp'); */ function handlePGPEncrypt(inputFilePath, publicKeyPath) { return new Promise((resolve, reject) => { - if (!inputFilePath || !publicKeyPath) { - return reject(new Error('Missing required options for encrypt operation')); - } let publicKeyArmored, fileData; try { publicKeyArmored = fs.readFileSync(publicKeyPath, 'utf8'); From b2dcfd081cf34cf0254fd458e8deb9885ab4628f Mon Sep 17 00:00:00 2001 From: mahmishr Date: Tue, 24 Jun 2025 08:28:46 +0530 Subject: [PATCH 5/9] comments resolved --- src/api/BatchUploadWithMTLSApi.js | 111 +++++++++++++----- src/authentication/logging/Logger.js | 45 +++---- .../BatchUpload/MutualAuthUploadUtility.js | 55 ++++++--- 3 files changed, 143 insertions(+), 68 deletions(-) diff --git a/src/api/BatchUploadWithMTLSApi.js b/src/api/BatchUploadWithMTLSApi.js index 5d5dd60d..6f797ea8 100644 --- a/src/api/BatchUploadWithMTLSApi.js +++ b/src/api/BatchUploadWithMTLSApi.js @@ -6,6 +6,9 @@ const { handleUploadOperationUsingPrivateKeyAndCerts } = require('../utilities/PGP/BatchUpload/MutualAuthUploadUtility'); const BatchUploadUtility = require('../utilities/PGP/BatchUpload/BatchUploadUtility'); +const LogConfiguration = require('../authentication/logging/LogConfiguration'); +const Constants = require('../authentication/util/Constants'); +const Logger = require('../authentication/logging/Logger'); /** * BatchUploadWithMTLSApi @@ -14,22 +17,43 @@ const BatchUploadUtility = require('../utilities/PGP/BatchUpload/BatchUploadUtil * Handles PGP encryption of files before upload. */ class BatchUploadWithMTLSApi { - constructor(logger = console) { - this.logger = logger; + /** + * Constructs a new BatchUploadWithMTLSApi instance. + * @param {LogConfiguration} log_config - Logging configuration object. + */ + constructor(log_config) { + const logConfiguration = new LogConfiguration(log_config); + //fallback for missing values + logConfiguration.getDefaultLoggingProperties(""); + + this.logger = Logger.getLoggerFromLogConfig(logConfiguration, 'BatchUploadWithMTLSApi'); + this.logger.info(Constants.BEGIN_TRANSACTION); } - uploadBatchAPIWithP12(opts, callback) { + /** + * Uploads a batch file to CyberSource using a PKCS#12 (.p12/.pfx) client certificate for mutual TLS authentication. + * The file is PGP-encrypted before upload. + * + * @param {Object} opts - Options for the upload. + * @param {string} opts.inputFilePath - Path to the input file to be uploaded. + * @param {string} opts.environmentHostname - CyberSource environment hostname. + * @param {string} opts.publicKeyFilePath - Path to the PGP public key file for encryption. + * @param {string} opts.clientCertP12FilePath - Path to the PKCS#12 client certificate file. + * @param {string} opts.clientCertP12Password - Password for the PKCS#12 client certificate password. + * @param {string} opts.serverTrustCertPath - Path to the server trust certificate file (optional). + * @param {boolean} [opts.verify_ssl=true] - Whether to reject unauthorized SSL certificates (optional). + * @param {function(Error, any):void} callback - Callback function with (error, result). + */ + uploadBatchAPIWithP12(inputFilePath, + environmentHostname, + publicKeyFilePath, + clientCertP12FilePath, + clientCertP12Password, + serverTrustCertPath, + verify_ssl = true, + callback) { try { - const { - inputFilePath, - environmentHostname, - publicKeyFilePath, - clientCertP12FilePath, - clientCertP12Password, - serverTrustCertPath, - rejectUnauthorizedFlag = true - } = opts; - if (rejectUnauthorizedFlag === false) { + if (verify_ssl === false) { this.logger.warn('rejectUnauthorized is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); } this.logger.info('Starting batch upload with p12/pfx for given file'); @@ -53,34 +77,52 @@ class BatchUploadWithMTLSApi { clientCertP12, clientCertP12Password, serverTrustCert, - rejectUnauthorizedFlag + verify_ssl ); }) - .then(result => callback(null, result)) + .then(result => { + callback(null, result); + this.logger.info(Constants.END_TRANSACTION); + }) .catch(error => { this.logger.error(error); - callback(error); + callback(error, error.response); + this.logger.info(Constants.END_TRANSACTION); }); } catch (e) { this.logger.error('Exception in Batch Upload API', e); callback(e); + this.logger.info(Constants.END_TRANSACTION); } } - uploadBatchAPIWithKeys(opts, callback) { - try { - const { - inputFilePath, - environmentHostname, - publicKeyFilePath, - clientPrivateKeyFilePath, - clientCertFilePath, - serverTrustCertPath, - clientKeyPassword, - rejectUnauthorizedFlag = true + /** + * Uploads a batch file to CyberSource using a client private key and certificate for mutual TLS authentication. + * The file is PGP-encrypted before upload. + * + * @param {Object} opts - Options for the upload. + * @param {string} opts.inputFilePath - Path to the input file to be uploaded. + * @param {string} opts.environmentHostname - CyberSource environment hostname. + * @param {string} opts.publicKeyFilePath - Path to the PGP public key file for encryption. + * @param {string} opts.clientPrivateKeyFilePath - Path to the client private key file. + * @param {string} opts.clientCertFilePath - Path to the client certificate file. + * @param {string} opts.serverTrustCertPath - Path to the server trust certificate file (optional). + * @param {string} [opts.clientKeyPassword] - Password for the client private key (if encrypted). + * @param {boolean} [opts.verify_ssl=true] - Whether to reject unauthorized SSL certificates (optional). + * @param {function(Error, any):void} callback - Callback function with (error, result). + */ - } = opts; - if (rejectUnauthorizedFlag === false) { + uploadBatchAPIWithKeys(inputFilePath, + environmentHostname, + publicKeyFilePath, + clientPrivateKeyFilePath, + clientCertFilePath, + serverTrustCertPath, + clientKeyPassword, + verify_ssl = true, + callback) { + try { + if (verify_ssl === false) { this.logger.warn('rejectUnauthorized is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); } this.logger.info('Starting batch upload with client private key and certs for given file'); @@ -105,17 +147,22 @@ class BatchUploadWithMTLSApi { clientCert, serverTrustCert, clientKeyPassword, - rejectUnauthorizedFlag + verify_ssl ); }) - .then(result => callback(null, result)) + .then(result => { + callback(null, result); + this.logger.info(Constants.END_TRANSACTION); + }) .catch(error => { this.logger.error(error); - callback(error); + callback(error, error.response); + this.logger.info(Constants.END_TRANSACTION); }); } catch (e) { this.logger.error('Exception in Batch Upload API', e); callback(e); + this.logger.info(Constants.END_TRANSACTION); } } } diff --git a/src/authentication/logging/Logger.js b/src/authentication/logging/Logger.js index 0b7679b9..d0d7096e 100644 --- a/src/authentication/logging/Logger.js +++ b/src/authentication/logging/Logger.js @@ -14,15 +14,19 @@ const unmaskedLoggingFormat = printf(({ level, message, label, timestamp }) => { }); exports.getLogger = function (merchantConfig, loggerCategory = 'UnknownCategoryLogger') { + return exports.getLoggerFromLogConfig(merchantConfig.getLogConfiguration(), loggerCategory); +} + +exports.getLoggerFromLogConfig = function (logConfig, loggerCategory = 'UnknownCategoryLogger') { - if(merchantConfig.getLogConfiguration().isExternalLoggerSet() && merchantConfig.getLogConfiguration().getExternalLogger() - && merchantConfig.getLogConfiguration().getExternalLogger().getLogger() - && merchantConfig.getLogConfiguration().getExternalLogger() instanceof ExternalLoggerWrapper){ - let logger = merchantConfig.getLogConfiguration().getExternalLogger().getLogger(); + if (logConfig.isExternalLoggerSet() && logConfig.getExternalLogger() + && logConfig.getExternalLogger().getLogger() + && logConfig.getExternalLogger() instanceof ExternalLoggerWrapper) { + let logger = logConfig.getExternalLogger().getLogger(); return logger; } - var enableLog = merchantConfig.getLogConfiguration().isLogEnabled(); - var enableMasking = merchantConfig.getLogConfiguration().isMaskingEnabled(); + var enableLog = logConfig.isLogEnabled(); + var enableMasking = logConfig.isMaskingEnabled(); var loggerCategoryRandomiser = Math.floor(Math.random() * (1000000000 - 100 + 1)) + 100; loggerCategory = loggerCategory + loggerCategoryRandomiser; @@ -30,16 +34,16 @@ exports.getLogger = function (merchantConfig, loggerCategory = 'UnknownCategoryL var newLogger; if (enableLog) { - var appTransports = createTransportFromConfig(merchantConfig); + var appTransports = createTransportFromConfig(logConfig); - var loggingLevel = merchantConfig.getLogConfiguration().getLoggingLevel(); + var loggingLevel = logConfig.getLoggingLevel(); newLogger = winston.loggers.get(loggerCategory, { level: loggingLevel, format: combine( - label({ label: loggerCategory }), - timestamp(), - enableMasking ? maskedLoggingFormat : unmaskedLoggingFormat + label({ label: loggerCategory }), + timestamp(), + enableMasking ? maskedLoggingFormat : unmaskedLoggingFormat ), transports: appTransports }); @@ -47,9 +51,9 @@ exports.getLogger = function (merchantConfig, loggerCategory = 'UnknownCategoryL newLogger = winston.loggers.get(loggerCategory, { level: loggingLevel, format: combine( - label({ label: loggerCategory }), - timestamp(), - enableMasking ? maskedLoggingFormat : unmaskedLoggingFormat + label({ label: loggerCategory }), + timestamp(), + enableMasking ? maskedLoggingFormat : unmaskedLoggingFormat ), transports: [new winston.transports.Console({ silent: !enableLog @@ -60,14 +64,15 @@ exports.getLogger = function (merchantConfig, loggerCategory = 'UnknownCategoryL return newLogger; } -function createTransportFromConfig(mConfig) { + +function createTransportFromConfig(logConfig) { var transports = []; - var enableLog = mConfig.getLogConfiguration().isLogEnabled(); - var loggingLevel = mConfig.getLogConfiguration().getLoggingLevel(); - var maxLogFiles = mConfig.getLogConfiguration().getMaxLogFiles(); - var logFileName = mConfig.getLogConfiguration().getLogFileName(); - var logDirectory = mConfig.getLogConfiguration().getLogDirectory(); + var enableLog = logConfig.isLogEnabled(); + var loggingLevel = logConfig.getLoggingLevel(); + var maxLogFiles = logConfig.getMaxLogFiles(); + var logFileName = logConfig.getLogFileName(); + var logDirectory = logConfig.getLogDirectory(); transports.push(new winston.transports.DailyRotateFile({ level: loggingLevel, diff --git a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js index 7d204de9..f8978804 100644 --- a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js +++ b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js @@ -23,10 +23,11 @@ function handleUploadOperationUsingP12orPfx( clientCertP12, clientCertP12Password, serverTrustCert, - rejectUnauthorizedFlag + verify_ssl ) { const form = new FormData(); - form.append('file', encryptedPgpBytes, { filename: fileName }); + const safeFileName = fileName && fileName.trim() ? fileName : 'file.pgp'; + form.append('file', encryptedPgpBytes, { filename: safeFileName }); const defaultCAs = tls.rootCertificates.slice(); //Assuming serverTrustCert is a PEM encoded certificate @@ -40,7 +41,7 @@ function handleUploadOperationUsingP12orPfx( pfx: clientCertP12, passphrase: clientCertP12Password, ca: defaultCAs, - rejectUnauthorized: rejectUnauthorizedFlag, + rejectUnauthorized: verify_ssl, servername: environmentHostname }); @@ -51,13 +52,16 @@ function handleUploadOperationUsingP12orPfx( 'v-c-correlation-id': correlationId } }).then(response => { - if (response.status === 201) { + if (response.status >= 200 && response.status < 300) { return normalizeResponse(response); } throw { response }; }).catch(error => { - return Promise.reject(normalizeError(error)); + return Promise.reject({ + error: normalizeErrorResponse(error), + response: error.response + }); }); } @@ -81,10 +85,11 @@ function handleUploadOperationUsingPrivateKeyAndCerts( clientCert, serverTrustCert, clientKeyPassword, - rejectUnauthorizedFlag + verify_ssl ) { const form = new FormData(); - form.append('file', encryptedPgpBytes, { filename: fileName }); + const safeFileName = fileName && fileName.trim() ? fileName : 'file.pgp'; + form.append('file', encryptedPgpBytes, { filename: safeFileName}); const correlationId = uuidv4(); @@ -98,7 +103,7 @@ function handleUploadOperationUsingPrivateKeyAndCerts( cert: clientCert, ca: defaultCAs, passphrase: clientKeyPassword, - rejectUnauthorized: rejectUnauthorizedFlag, + rejectUnauthorized: verify_ssl, servername: environmentHostname }); @@ -109,13 +114,16 @@ function handleUploadOperationUsingPrivateKeyAndCerts( 'v-c-correlation-id': correlationId } }).then(response => { - if (response.status === 201) { + if (response.status >= 200 && response.status < 300) { return normalizeResponse(response); } throw { response }; - }).catch(error => { - return Promise.reject(normalizeError(error)); + }).catch(function(error) { + return Promise.reject({ + error: normalizeErrorResponse(error), + response: error.response + }); }); } @@ -124,16 +132,31 @@ function normalizeResponse(response) { status: response.status, statusText: response.statusText, data: response.data, + header: response.headers, message: "File uploaded successfully" }; } -function normalizeError(error) { - return { - message: `File upload failed: ${error.message}` - }; +function normalizeErrorResponse(error) { + if (typeof error.response !== 'undefined') { + return { + status: error.response.status, + statusText: error.response.statusText, + data: error.response.data, + header: error.response.headers, + message: `File upload failed: ${error.message}` + }; + } else { + var tester = {}; + tester.errno = error.errno; + tester.code = error.code; + tester.syscall = error.syscall; + tester.address = error.address; + tester.port = error.port; + tester.message = error.message ? error.message : 'An unknown error occurred'; + return tester; + } } - module.exports = { handleUploadOperationUsingP12orPfx, handleUploadOperationUsingPrivateKeyAndCerts From 4cc5533916a6d96f82b908f282cbecdb947d2cbc Mon Sep 17 00:00:00 2001 From: mahmishr Date: Wed, 25 Jun 2025 09:31:20 +0530 Subject: [PATCH 6/9] opts change --- src/api/BatchUploadWithMTLSApi.js | 67 ++++++++++--------- .../PGP/BatchUpload/BatchUploadUtility.js | 10 +-- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/api/BatchUploadWithMTLSApi.js b/src/api/BatchUploadWithMTLSApi.js index 6f797ea8..c2ab3bd8 100644 --- a/src/api/BatchUploadWithMTLSApi.js +++ b/src/api/BatchUploadWithMTLSApi.js @@ -1,10 +1,7 @@ const path = require('path'); const fs = require('fs'); -const { handlePGPEncrypt } = require('../utilities/PGP/BatchUpload/PgpEncryptionUtility'); -const { - handleUploadOperationUsingP12orPfx, - handleUploadOperationUsingPrivateKeyAndCerts -} = require('../utilities/PGP/BatchUpload/MutualAuthUploadUtility'); +const PgpEncryptionUtility = require('../utilities/PGP/BatchUpload/PgpEncryptionUtility'); +const MutualAuthUploadUtility = require('../utilities/PGP/BatchUpload/MutualAuthUploadUtility'); const BatchUploadUtility = require('../utilities/PGP/BatchUpload/BatchUploadUtility'); const LogConfiguration = require('../authentication/logging/LogConfiguration'); const Constants = require('../authentication/util/Constants'); @@ -22,6 +19,11 @@ class BatchUploadWithMTLSApi { * @param {LogConfiguration} log_config - Logging configuration object. */ constructor(log_config) { + if (!log_config) { + log_config = { + enableLog: false + }; + } const logConfiguration = new LogConfiguration(log_config); //fallback for missing values logConfiguration.getDefaultLoggingProperties(""); @@ -44,17 +46,19 @@ class BatchUploadWithMTLSApi { * @param {boolean} [opts.verify_ssl=true] - Whether to reject unauthorized SSL certificates (optional). * @param {function(Error, any):void} callback - Callback function with (error, result). */ - uploadBatchAPIWithP12(inputFilePath, - environmentHostname, - publicKeyFilePath, - clientCertP12FilePath, - clientCertP12Password, - serverTrustCertPath, - verify_ssl = true, - callback) { + uploadBatchAPIWithP12(opts, callback) { + const { + inputFilePath, + environmentHostname, + publicKeyFilePath, + clientCertP12FilePath, + clientCertP12Password, + serverTrustCertPath, + verify_ssl = true + } = opts; try { if (verify_ssl === false) { - this.logger.warn('rejectUnauthorized is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); + this.logger.warn('verify_ssl is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); } this.logger.info('Starting batch upload with p12/pfx for given file'); const endpoint = '/pts/v1/transaction-batch-upload'; @@ -63,13 +67,12 @@ class BatchUploadWithMTLSApi { inputFilePath, environmentHostname, publicKeyFilePath, clientCertP12FilePath, serverTrustCertPath ); - handlePGPEncrypt(inputFilePath, publicKeyFilePath) + PgpEncryptionUtility.handlePGPEncrypt(inputFilePath, publicKeyFilePath) .then(encryptedBuffer => { const uploadFileName = path.basename(inputFilePath) + '.pgp'; - console.log('Encrypted file name:', uploadFileName); const clientCertP12 = fs.readFileSync(clientCertP12FilePath); const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; - return handleUploadOperationUsingP12orPfx( + return MutualAuthUploadUtility.handleUploadOperationUsingP12orPfx( encryptedBuffer, endpointUrl, environmentHostname, @@ -86,7 +89,7 @@ class BatchUploadWithMTLSApi { }) .catch(error => { this.logger.error(error); - callback(error, error.response); + callback(error, (error && error.response) ? error.response : undefined); this.logger.info(Constants.END_TRANSACTION); }); } catch (e) { @@ -112,18 +115,20 @@ class BatchUploadWithMTLSApi { * @param {function(Error, any):void} callback - Callback function with (error, result). */ - uploadBatchAPIWithKeys(inputFilePath, - environmentHostname, - publicKeyFilePath, - clientPrivateKeyFilePath, - clientCertFilePath, - serverTrustCertPath, - clientKeyPassword, - verify_ssl = true, - callback) { + uploadBatchAPIWithKeys(opts, callback) { + const { + inputFilePath, + environmentHostname, + publicKeyFilePath, + clientPrivateKeyFilePath, + clientCertFilePath, + serverTrustCertPath, + clientKeyPassword, + verify_ssl = true + } = opts; try { if (verify_ssl === false) { - this.logger.warn('rejectUnauthorized is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); + this.logger.warn('verify_ssl is set to false. SSL verification is DISABLED. This setting is NOT SAFE for production and should NOT be used in production environments!'); } this.logger.info('Starting batch upload with client private key and certs for given file'); const endpoint = '/pts/v1/transaction-batch-upload'; @@ -132,13 +137,13 @@ class BatchUploadWithMTLSApi { inputFilePath, environmentHostname, publicKeyFilePath, clientPrivateKeyFilePath, clientCertFilePath, serverTrustCertPath ); - handlePGPEncrypt(inputFilePath, publicKeyFilePath) + PgpEncryptionUtility.handlePGPEncrypt(inputFilePath, publicKeyFilePath) .then(encryptedBuffer => { const uploadFileName = path.basename(inputFilePath) + '.pgp'; const clientPrivateKey = fs.readFileSync(clientPrivateKeyFilePath); const clientCert = fs.readFileSync(clientCertFilePath); const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; - return handleUploadOperationUsingPrivateKeyAndCerts( + return MutualAuthUploadUtility.handleUploadOperationUsingPrivateKeyAndCerts( encryptedBuffer, endpointUrl, environmentHostname, @@ -156,7 +161,7 @@ class BatchUploadWithMTLSApi { }) .catch(error => { this.logger.error(error); - callback(error, error.response); + callback(error, (error && error.response) ? error.response : undefined); this.logger.info(Constants.END_TRANSACTION); }); } catch (e) { diff --git a/src/utilities/PGP/BatchUpload/BatchUploadUtility.js b/src/utilities/PGP/BatchUpload/BatchUploadUtility.js index d0260113..c83ddc7b 100644 --- a/src/utilities/PGP/BatchUpload/BatchUploadUtility.js +++ b/src/utilities/PGP/BatchUpload/BatchUploadUtility.js @@ -25,9 +25,9 @@ class BatchUploadUtility { * Validates the input parameters for batch API using P12 client certificate. * @param {string} inputFile - Path to the input CSV file for batch upload. * @param {string} environmentHostname - * @param {string} pgpEncryptionCertPath - * @param {string} clientCertP12FilePath - * @param {string} serverTrustCertPath + * @param {string} pgpEncryptionCertPath - Path to the PGP public key file (.asc). + * @param {string} clientCertP12FilePath - Path to the client P12 certificate file. + * @param {string} serverTrustCertPath - Path to the server trust certificate file (PEM, optional). */ static validateBatchApiP12Inputs(inputFile, environmentHostname, pgpEncryptionCertPath, clientCertP12FilePath, serverTrustCertPath) { this.validateInputFile(inputFile); @@ -48,8 +48,8 @@ class BatchUploadUtility { * @param {string} environmentHostname * @param {string} pgpPublicKeyPath - Path to the PGP public key file (.asc). * @param {string} clientPrivateKeyPath - Path to the client private key file (PEM). - * @param {string} clientCertPath - Path to the client X509 certificate file (PEM). - * @param {string} serverTrustCertPath - Path to the server trust X509 certificate file (PEM, optional). + * @param {string} clientCertPath - Path to the client certificate file (PEM). + * @param {string} serverTrustCertPath - Path to the server trust certificate file (PEM, optional). */ static validateBatchApiKeysInputs(inputFile, environmentHostname, pgpPublicKeyPath, clientPrivateKeyPath, clientCertPath, serverTrustCertPath) { this.validateInputFile(inputFile); From f2ab7da07ee003f98d8fcaa49e9848b4785bb11f Mon Sep 17 00:00:00 2001 From: mahmishr Date: Thu, 26 Jun 2025 12:15:10 +0530 Subject: [PATCH 7/9] server name and log error chngs --- src/api/BatchUploadWithMTLSApi.js | 24 +++++++++++++++---- .../PGP/BatchUpload/BatchUploadUtility.js | 2 +- .../BatchUpload/MutualAuthUploadUtility.js | 13 +++++----- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/api/BatchUploadWithMTLSApi.js b/src/api/BatchUploadWithMTLSApi.js index c2ab3bd8..81ba1d53 100644 --- a/src/api/BatchUploadWithMTLSApi.js +++ b/src/api/BatchUploadWithMTLSApi.js @@ -16,7 +16,21 @@ const Logger = require('../authentication/logging/Logger'); class BatchUploadWithMTLSApi { /** * Constructs a new BatchUploadWithMTLSApi instance. - * @param {LogConfiguration} log_config - Logging configuration object. + * @param {Object} [log_config] - Logging configuration object (optional). + * @param {boolean} [log_config.enableLog=false] - Enable or disable logging. + * @param {string} [log_config.logFileName='cybs-batch-upload'] - Log file name (without extension). + * @param {string} [log_config.logDirectory='./logs'] - Directory to store log files. + * @param {number} [log_config.logFileMaxSize=5242880] - Maximum log file size in bytes (default 5MB). + * @param {string} [log_config.loggingLevel='debug'] - Logging level ('debug', 'info', 'warn', 'error'). + * + * Example: + * const log_config = { + * enableLog: true, + * logFileName: 'cybs-batch-upload', + * logDirectory: './logs', + * logFileMaxSize: 5242880, + * loggingLevel: 'debug' + * }; */ constructor(log_config) { if (!log_config) { @@ -75,7 +89,6 @@ class BatchUploadWithMTLSApi { return MutualAuthUploadUtility.handleUploadOperationUsingP12orPfx( encryptedBuffer, endpointUrl, - environmentHostname, uploadFileName, clientCertP12, clientCertP12Password, @@ -88,7 +101,8 @@ class BatchUploadWithMTLSApi { this.logger.info(Constants.END_TRANSACTION); }) .catch(error => { - this.logger.error(error); + const errorMsg = error?.message || error?.error?.message || error.stack; + this.logger.error(errorMsg); callback(error, (error && error.response) ? error.response : undefined); this.logger.info(Constants.END_TRANSACTION); }); @@ -146,7 +160,6 @@ class BatchUploadWithMTLSApi { return MutualAuthUploadUtility.handleUploadOperationUsingPrivateKeyAndCerts( encryptedBuffer, endpointUrl, - environmentHostname, uploadFileName, clientPrivateKey, clientCert, @@ -160,7 +173,8 @@ class BatchUploadWithMTLSApi { this.logger.info(Constants.END_TRANSACTION); }) .catch(error => { - this.logger.error(error); + const errorMsg = error?.message || error?.error?.message || error.stack; + this.logger.error(errorMsg); callback(error, (error && error.response) ? error.response : undefined); this.logger.info(Constants.END_TRANSACTION); }); diff --git a/src/utilities/PGP/BatchUpload/BatchUploadUtility.js b/src/utilities/PGP/BatchUpload/BatchUploadUtility.js index c83ddc7b..82701024 100644 --- a/src/utilities/PGP/BatchUpload/BatchUploadUtility.js +++ b/src/utilities/PGP/BatchUpload/BatchUploadUtility.js @@ -96,7 +96,7 @@ class BatchUploadUtility { } // Normalize Windows-style paths that start with a slash before the drive letter - let normalizedPath = filePath; + let normalizedPath = filePath.trim(); if (path.sep === '\\' && normalizedPath.match(/^\/[A-Za-z]:.*/)) { normalizedPath = normalizedPath.substring(1); } diff --git a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js index f8978804..1596dfdf 100644 --- a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js +++ b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js @@ -8,7 +8,6 @@ const { v4: uuidv4 } = require('uuid'); /** * Uploads an encrypted PGP file using mTLS with PKCS#12 (.p12/.pfx) client certificate. * @param {Buffer} encryptedPgpBytes - The encrypted PGP file content as Buffer. - * @param {string} environmentHostname - The environment hostname. * @param {string} fileName - The name of the file to be uploaded. * @param {Buffer} clientCertP12 - The PKCS#12 client certificate as Buffer. * @param {string} clientCertP12Password - Password for the PKCS#12 client certificate. @@ -18,7 +17,6 @@ const { v4: uuidv4 } = require('uuid'); function handleUploadOperationUsingP12orPfx( encryptedPgpBytes, endpointUrl, - environmentHostname, fileName, clientCertP12, clientCertP12Password, @@ -37,12 +35,14 @@ function handleUploadOperationUsingP12orPfx( const correlationId = uuidv4(); + const cleanServername = new URL(endpointUrl).hostname; + const httpsAgent = new https.Agent({ pfx: clientCertP12, passphrase: clientCertP12Password, ca: defaultCAs, rejectUnauthorized: verify_ssl, - servername: environmentHostname + servername: cleanServername }); return axios.post(endpointUrl, form, { @@ -68,7 +68,6 @@ function handleUploadOperationUsingP12orPfx( /** * Uploads an encrypted PGP file using mTLS with client key/cert as PEM Buffers/strings. * @param {Buffer} encryptedPgpBytes - The encrypted PGP file content as Buffer. - * @param {string} environmentHostname - The environment hostname. * @param {string} fileName - The name of the file to be uploaded. * @param {Buffer|string} clientPrivateKey - The client's private key (Buffer). * @param {Buffer|string} clientCert - The client's certificate (Buffer). @@ -79,7 +78,6 @@ function handleUploadOperationUsingP12orPfx( function handleUploadOperationUsingPrivateKeyAndCerts( encryptedPgpBytes, endpointUrl, - environmentHostname, fileName, clientPrivateKey, clientCert, @@ -98,13 +96,16 @@ function handleUploadOperationUsingPrivateKeyAndCerts( if (serverTrustCert && serverTrustCert.length > 0) { defaultCAs.push(serverTrustCert.toString()); } + + const cleanServername = new URL(endpointUrl).hostname; + const httpsAgent = new https.Agent({ key: clientPrivateKey, cert: clientCert, ca: defaultCAs, passphrase: clientKeyPassword, rejectUnauthorized: verify_ssl, - servername: environmentHostname + servername: cleanServername }); return axios.post(endpointUrl, form, { From 870af5cdb36389cdea413bf54d83fcca573b5f1d Mon Sep 17 00:00:00 2001 From: mahmishr Date: Wed, 9 Jul 2025 15:42:32 +0530 Subject: [PATCH 8/9] minor fix --- package.json | 1 + src/api/BatchUploadWithMTLSApi.js | 12 ++++++++++-- .../PGP/BatchUpload/MutualAuthUploadUtility.js | 6 ++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3b860067..d15b85af 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "memory-cache": "^0.2.0", "node-forge": ">=1.0.0", "openpgp": "^5.11.3", + "openpgp": "^6.1.1", "promise": "^8.3.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", diff --git a/src/api/BatchUploadWithMTLSApi.js b/src/api/BatchUploadWithMTLSApi.js index 81ba1d53..9b398afa 100644 --- a/src/api/BatchUploadWithMTLSApi.js +++ b/src/api/BatchUploadWithMTLSApi.js @@ -83,7 +83,11 @@ class BatchUploadWithMTLSApi { PgpEncryptionUtility.handlePGPEncrypt(inputFilePath, publicKeyFilePath) .then(encryptedBuffer => { - const uploadFileName = path.basename(inputFilePath) + '.pgp'; + let uploadFileName = 'file.pgp'; + if (inputFilePath && inputFilePath.trim()) { + const base = path.basename(inputFilePath, path.extname(inputFilePath)); + uploadFileName = base + '.pgp'; + } const clientCertP12 = fs.readFileSync(clientCertP12FilePath); const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; return MutualAuthUploadUtility.handleUploadOperationUsingP12orPfx( @@ -153,7 +157,11 @@ class BatchUploadWithMTLSApi { PgpEncryptionUtility.handlePGPEncrypt(inputFilePath, publicKeyFilePath) .then(encryptedBuffer => { - const uploadFileName = path.basename(inputFilePath) + '.pgp'; + let uploadFileName = 'file.pgp'; + if (inputFilePath && inputFilePath.trim()) { + const base = path.basename(inputFilePath, path.extname(inputFilePath)); + uploadFileName = base + '.pgp'; + } const clientPrivateKey = fs.readFileSync(clientPrivateKeyFilePath); const clientCert = fs.readFileSync(clientCertFilePath); const serverTrustCert = serverTrustCertPath ? fs.readFileSync(serverTrustCertPath) : undefined; diff --git a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js index 1596dfdf..4aaa829f 100644 --- a/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js +++ b/src/utilities/PGP/BatchUpload/MutualAuthUploadUtility.js @@ -24,8 +24,7 @@ function handleUploadOperationUsingP12orPfx( verify_ssl ) { const form = new FormData(); - const safeFileName = fileName && fileName.trim() ? fileName : 'file.pgp'; - form.append('file', encryptedPgpBytes, { filename: safeFileName }); + form.append('file', encryptedPgpBytes, { filename: fileName }); const defaultCAs = tls.rootCertificates.slice(); //Assuming serverTrustCert is a PEM encoded certificate @@ -86,8 +85,7 @@ function handleUploadOperationUsingPrivateKeyAndCerts( verify_ssl ) { const form = new FormData(); - const safeFileName = fileName && fileName.trim() ? fileName : 'file.pgp'; - form.append('file', encryptedPgpBytes, { filename: safeFileName}); + form.append('file', encryptedPgpBytes, { filename: fileName }); const correlationId = uuidv4(); From e16e6309aa717ec43e50e277714175783c7f4dde Mon Sep 17 00:00:00 2001 From: mahmishr Date: Wed, 9 Jul 2025 15:49:11 +0530 Subject: [PATCH 9/9] Update package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d15b85af..624a2ea1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "jwt-simple": "^0.5.6", "memory-cache": "^0.2.0", "node-forge": ">=1.0.0", - "openpgp": "^5.11.3", "openpgp": "^6.1.1", "promise": "^8.3.0", "winston": "^3.11.0",