Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e0a388
added config options for message leveencryption for response
monu-kumar-visa Sep 15, 2025
d54855d
added utility for reading private key and changes for decrypting encr…
monu-kumar-visa Sep 17, 2025
e7a6b9d
corrected request mle check
monu-kumar-visa Sep 17, 2025
7b46d5c
corrected check and validation of request mle
monu-kumar-visa Sep 18, 2025
90e5754
minor fixs
monu-kumar-visa Sep 18, 2025
a6106ec
minor fix
monu-kumar-visa Sep 18, 2025
4fae8de
corrected file path check and renamed mleKeyAlias to requestMleKeyAlias
monu-kumar-visa Sep 19, 2025
e3fde6c
decrypting response in case of encrypted response
monu-kumar-visa Sep 19, 2025
45eb1a0
corrected log message
monu-kumar-visa Sep 22, 2025
00c3e6c
mustache field changes
monu-kumar-visa Sep 22, 2025
0cafe70
changing config val to string
monu-kumar-visa Sep 23, 2025
ccf235d
changing JSON string to object
monu-kumar-visa Sep 24, 2025
bd4d269
logging file name
monu-kumar-visa Sep 24, 2025
a8565ce
using a more descriptive message
monu-kumar-visa Sep 24, 2025
4414e13
updated MLE.md
monu-kumar-visa Sep 25, 2025
b69c2fb
accepting string/jwk object as mle repsone private key
monu-kumar-visa Sep 29, 2025
ec681cd
handelling encrypted pem string
monu-kumar-visa Sep 30, 2025
9835ddb
corrected err message
monu-kumar-visa Sep 30, 2025
1c55d18
addressing PR comments
monu-kumar-visa Oct 6, 2025
4c2d30e
Merge remote-tracking branch 'origin/future' into mle-for-response
monu-kumar-visa Oct 6, 2025
40e0d22
minor fix
monu-kumar-visa Oct 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
452 changes: 403 additions & 49 deletions MLE.md

Large diffs are not rendered by default.

73 changes: 51 additions & 22 deletions generator/cybersource-javascript-template/ApiClient.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ const agentPool = new Map();
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['axios', 'axios-cookiejar-support', 'https-proxy-agent', 'https', 'querystring', 'agentkeepalive', 'crypto', 'Authentication/MerchantConfig', 'Authentication/Logger', 'Authentication/Constants', 'Authentication/Authorization', 'Authentication/PayloadDigest'], factory);
define(['axios', 'axios-cookiejar-support', 'https-proxy-agent', 'https', 'querystring', 'agentkeepalive', 'crypto', 'Authentication/MerchantConfig', 'Authentication/Logger', 'Authentication/Constants', 'Authentication/Authorization', 'Authentication/PayloadDigest', 'Authentication/MLEUtility'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports, like Node.
module.exports = factory(require('axios'), require('axios-cookiejar-support'), require('https-proxy-agent'), require('https'), require('querystring'), require('agentkeepalive'), require('crypto'), require('./authentication/core/MerchantConfig'), require('./authentication/logging/Logger'), require('./authentication/util/Constants'), require('./authentication/core/Authorization'), require('./authentication/payloadDigest/DigestGenerator'));
module.exports = factory(require('axios'), require('axios-cookiejar-support'), require('https-proxy-agent'), require('https'), require('querystring'), require('agentkeepalive'), require('crypto'), require('./authentication/core/MerchantConfig'), require('./authentication/logging/Logger'), require('./authentication/util/Constants'), require('./authentication/core/Authorization'), require('./authentication/payloadDigest/DigestGenerator'), require('./authentication/util/MLEUtility'));
} else {
// Browser globals (root is window)
if (!root.{{moduleName}}) {
root.{{moduleName}} = {};
}
root.{{moduleName}}.ApiClient = factory(root.axios, root.axiosCookieJar, root.httpsProxyAgent, root.https, root.querystring, root.AgentKeepAlive, root.crypto, root.Authentication.MerchantConfig, root.Authentication.Logger, root.Authentication.Constants, root.Authentication.Authorization, root.Authentication.PayloadDigest);
root.{{moduleName}}.ApiClient = factory(root.axios, root.axiosCookieJar, root.httpsProxyAgent, root.https, root.querystring, root.AgentKeepAlive, root.crypto, root.Authentication.MerchantConfig, root.Authentication.Logger, root.Authentication.Constants, root.Authentication.Authorization, root.Authentication.PayloadDigest, root.Authentication.MLEUtility);
}
}(this, function(axios, axiosCookieJar, HttpsProxyAgent, https, querystring, AgentKeepAlive, crypto, MerchantConfig, Logger, Constants, Authorization, PayloadDigest) {
}(this, function(axios, axiosCookieJar, HttpsProxyAgent, https, querystring, AgentKeepAlive, crypto, MerchantConfig, Logger, Constants, Authorization, PayloadDigest, MLEUtility) {
{{#emitJSDoc}} /**
* @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient
* @version {{projectVersion}}
Expand Down Expand Up @@ -573,8 +573,9 @@ const agentPool = new Map();
* @param {String} httpMethod
* @param {String} requestTarget
* @param {String} requestBody
* @param {Boolean} isResponseMLEForApi
*/
exports.prototype.callAuthenticationHeader = function (httpMethod, requestTarget, requestBody, headerParams) {
exports.prototype.callAuthenticationHeader = function (httpMethod, requestTarget, requestBody, headerParams, isResponseMLEForApi) {

this.merchantConfig.setRequestTarget(requestTarget);
this.merchantConfig.setRequestType(httpMethod)
Expand All @@ -583,7 +584,7 @@ const agentPool = new Map();
this.logger.info('Authentication Type : ' + this.merchantConfig.getAuthenticationType());
this.logger.info(this.constants.REQUEST_TYPE + ' : ' + httpMethod.toUpperCase());

var token = Authorization.getToken(this.merchantConfig, this.logger);
var token = Authorization.getToken(this.merchantConfig, isResponseMLEForApi, this.logger);

var clientId = getClientId();

Expand Down Expand Up @@ -658,13 +659,14 @@ const agentPool = new Map();
* @param {Array.<String>} contentTypes An array of request MIME types.
* @param {Array.<String>} accepts An array of acceptable response MIME types.
* @param {(String|Array|ObjectFunction)} returnType The required type to return; can be a string for simple types or the
* @param {Boolean} isResponseMLEForApi - Flag indicating if MLE is enabled for this API
* constructor for a complex type.{{^usePromises}}
* @param {module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient~callApiCallback} callback The callback function.{{/usePromises}}
* @returns {{#usePromises}}{Promise} A {@link https://www.promisejs.org/|Promise} object{{/usePromises}}{{^usePromises}}{Object} The SuperAgent request object{{/usePromises}}.
*/
{{/emitJSDoc}} exports.prototype.callApi = function callApi(path, httpMethod, pathParams,
queryParams, headerParams, formParams, bodyParam, authNames, contentTypes, accepts,
returnType{{^usePromises}}, callback{{/usePromises}}) {
returnType, isResponseMLEForApi{{^usePromises}}, callback{{/usePromises}}) {

var _this = this;
var url = this.buildUrl(path, pathParams);
Expand Down Expand Up @@ -741,7 +743,7 @@ const agentPool = new Map();

if (this.merchantConfig.getAuthenticationType().toLowerCase() !== this.constants.MUTUAL_AUTH)
{
headerParams = this.callAuthenticationHeader(httpMethod, requestTarget, bodyParam, headerParams);
headerParams = this.callAuthenticationHeader(httpMethod, requestTarget, bodyParam, headerParams, isResponseMLEForApi);
}

if(this.merchantConfig.getDefaultHeaders()) {
Expand Down Expand Up @@ -841,14 +843,27 @@ const agentPool = new Map();
axiosConfig.url = requestTarget;

{{#usePromises}} return axios.request(axiosConfig).then(function(response) {
try {
var data = _this.deserialize(response, returnType);
response = _this.translateResponse(response);

resolve({data: data, response: response});
} catch(err) {
reject(err);
}
// Properly wait for the decryption to complete before proceeding
return MLEUtility.checkAndDecryptEncryptedResponse(response.data, _this.merchantConfig)
.then(function(decryptedData) {
response.data = decryptedData;

try {
var data = _this.deserialize(response, returnType);
response = _this.translateResponse(response);

resolve({data: data, response: response});
} catch(err) {
reject(err);
}
})
.catch(function(error) {
// Create a simple error object with descriptive message
const errorMsg = `Failed to decrypt response: ${error.message}`;

// Reject the promise for Promise-based usage
return Promise.reject(new Error(errorMsg));
});
}).catch(function(error, response) {
source.cancel('Stream ended.');
var userError = {};
Expand All @@ -867,12 +882,26 @@ const agentPool = new Map();
reject(userError);
});{{/usePromises}}
{{^usePromises}} axios.request(axiosConfig).then(function(response) {
if (callback) {
var data = _this.deserialize(response, returnType);
response = _this.translateResponse(response);

callback(null, data, response);
}
// Properly wait for the decryption to complete before proceeding
return MLEUtility.checkAndDecryptEncryptedResponse(response.data, _this.merchantConfig)
.then(function(decryptedData) {
response.data = decryptedData;

if (callback) {
var data = _this.deserialize(response, returnType);
response = _this.translateResponse(response);

callback(null, data, response);
}
})
.catch(function(error) {
// Create a simple error object with descriptive message
const errorMsg = `Failed to decrypt response: ${error.message}`;

if (callback) {
callback(new Error(errorMsg), null, null);
}
});
}).catch(function(error, response) {
source.cancel('Stream ended.');
var userError = {};
Expand Down
5 changes: 3 additions & 2 deletions generator/cybersource-javascript-template/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,21 @@
//check isMLE for an api method 'this.<operationId>'
var inboundMLEStatus = <#vendorExtensions.x-devcenter-metaData.mleForRequest>'<vendorExtensions.x-devcenter-metaData.mleForRequest>'</vendorExtensions.x-devcenter-metaData.mleForRequest><^vendorExtensions.x-devcenter-metaData.mleForRequest>'false'</vendorExtensions.x-devcenter-metaData.mleForRequest>;
var isMLEForApi = MLEUtility.checkIsMLEForAPI(this.apiClient.merchantConfig, inboundMLEStatus, '<operationId>');
const isResponseMLEForApi = MLEUtility.checkIsResponseMLEForAPI(this.apiClient.merchantConfig, ['<operationId>']);

if (isMLEForApi === true) {
MLEUtility.encryptRequestPayload(this.apiClient.merchantConfig, postBody).then(postBody => {
return this.apiClient.callApi(
'<&path>', '<httpMethod>',
pathParams, queryParams, headerParams, formParams, postBody,
authNames, contentTypes, accepts, returnType<^usePromises>, callback</usePromises>
authNames, contentTypes, accepts, returnType, isResponseMLEForApi<^usePromises>, callback</usePromises>
);
});
} else {
return this.apiClient.callApi(
'<&path>', '<httpMethod>',
pathParams, queryParams, headerParams, formParams, postBody,
authNames, contentTypes, accepts, returnType<^usePromises>, callback</usePromises>
authNames, contentTypes, accepts, returnType, isResponseMLEForApi<^usePromises>, callback</usePromises>
);
}
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"promise": "^8.3.0",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^4.7.1",
"node-jose": "^2.2.0"
"node-jose": "^2.2.0",
"jwk-to-pem": "^2.0.7"
},
"keywords": [
"nodeJS"
Expand Down
56 changes: 42 additions & 14 deletions src/ApiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ const agentPool = new Map();
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['axios', 'axios-cookiejar-support', 'https-proxy-agent', 'https', 'querystring', 'agentkeepalive', 'crypto', 'Authentication/MerchantConfig', 'Authentication/Logger', 'Authentication/Constants', 'Authentication/Authorization', 'Authentication/PayloadDigest'], factory);
define(['axios', 'axios-cookiejar-support', 'https-proxy-agent', 'https', 'querystring', 'agentkeepalive', 'crypto', 'Authentication/MerchantConfig', 'Authentication/Logger', 'Authentication/Constants', 'Authentication/Authorization', 'Authentication/PayloadDigest', 'Authentication/MLEUtility'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports, like Node.
module.exports = factory(require('axios'), require('axios-cookiejar-support'), require('https-proxy-agent'), require('https'), require('querystring'), require('agentkeepalive'), require('crypto'), require('./authentication/core/MerchantConfig'), require('./authentication/logging/Logger'), require('./authentication/util/Constants'), require('./authentication/core/Authorization'), require('./authentication/payloadDigest/DigestGenerator'));
module.exports = factory(require('axios'), require('axios-cookiejar-support'), require('https-proxy-agent'), require('https'), require('querystring'), require('agentkeepalive'), require('crypto'), require('./authentication/core/MerchantConfig'), require('./authentication/logging/Logger'), require('./authentication/util/Constants'), require('./authentication/core/Authorization'), require('./authentication/payloadDigest/DigestGenerator'), require('./authentication/util/MLEUtility'));
} else {
// Browser globals (root is window)
if (!root.CyberSource) {
root.CyberSource = {};
}
root.CyberSource.ApiClient = factory(root.axios, root.axiosCookieJar, root.httpsProxyAgent, root.https, root.querystring, root.AgentKeepAlive, root.crypto, root.Authentication.MerchantConfig, root.Authentication.Logger, root.Authentication.Constants, root.Authentication.Authorization, root.Authentication.PayloadDigest);
root.CyberSource.ApiClient = factory(root.axios, root.axiosCookieJar, root.httpsProxyAgent, root.https, root.querystring, root.AgentKeepAlive, root.crypto, root.Authentication.MerchantConfig, root.Authentication.Logger, root.Authentication.Constants, root.Authentication.Authorization, root.Authentication.PayloadDigest, root.Authentication.MLEUtility);
}
}(this, function(axios, axiosCookieJar, HttpsProxyAgent, https, querystring, AgentKeepAlive, crypto, MerchantConfig, Logger, Constants, Authorization, PayloadDigest) {
}(this, function(axios, axiosCookieJar, HttpsProxyAgent, https, querystring, AgentKeepAlive, crypto, MerchantConfig, Logger, Constants, Authorization, PayloadDigest, MLEUtility) {
/**
* @module ApiClient
* @version 0.0.1
Expand Down Expand Up @@ -577,8 +577,9 @@ const agentPool = new Map();
* @param {String} httpMethod
* @param {String} requestTarget
* @param {String} requestBody
* @param {Boolean} isResponseMLEForApi
*/
exports.prototype.callAuthenticationHeader = function (httpMethod, requestTarget, requestBody, headerParams) {
exports.prototype.callAuthenticationHeader = function (httpMethod, requestTarget, requestBody, headerParams, isResponseMLEForApi) {

this.merchantConfig.setRequestTarget(requestTarget);
this.merchantConfig.setRequestType(httpMethod)
Expand All @@ -587,7 +588,7 @@ const agentPool = new Map();
this.logger.info('Authentication Type : ' + this.merchantConfig.getAuthenticationType());
this.logger.info(this.constants.REQUEST_TYPE + ' : ' + httpMethod.toUpperCase());

var token = Authorization.getToken(this.merchantConfig, this.logger);
var token = Authorization.getToken(this.merchantConfig, isResponseMLEForApi, this.logger);

var clientId = getClientId();

Expand Down Expand Up @@ -662,13 +663,14 @@ const agentPool = new Map();
* @param {Array.<String>} contentTypes An array of request MIME types.
* @param {Array.<String>} accepts An array of acceptable response MIME types.
* @param {(String|Array|ObjectFunction)} returnType The required type to return; can be a string for simple types or the
* @param {Boolean} isResponseMLEForApi - Flag indicating if MLE is enabled for this API
* constructor for a complex type.
* @param {module:ApiClient~callApiCallback} callback The callback function.
* @returns {Object} The SuperAgent request object.
*/
exports.prototype.callApi = function callApi(path, httpMethod, pathParams,
queryParams, headerParams, formParams, bodyParam, authNames, contentTypes, accepts,
returnType, callback) {
returnType, isResponseMLEForApi, callback) {

var _this = this;
var url = this.buildUrl(path, pathParams);
Expand Down Expand Up @@ -745,7 +747,7 @@ const agentPool = new Map();

if (this.merchantConfig.getAuthenticationType().toLowerCase() !== this.constants.MUTUAL_AUTH)
{
headerParams = this.callAuthenticationHeader(httpMethod, requestTarget, bodyParam, headerParams);
headerParams = this.callAuthenticationHeader(httpMethod, requestTarget, bodyParam, headerParams, isResponseMLEForApi);
}

if(this.merchantConfig.getDefaultHeaders()) {
Expand Down Expand Up @@ -846,12 +848,38 @@ const agentPool = new Map();


axios.request(axiosConfig).then(function(response) {
if (callback) {
var data = _this.deserialize(response, returnType);
response = _this.translateResponse(response);

callback(null, data, response);
}
// Properly wait for the decryption to complete before proceeding
return MLEUtility.checkAndDecryptEncryptedResponse(response.data, _this.merchantConfig)
.then(function(decryptedData) {
response.data = decryptedData;

if (callback) {
var data = _this.deserialize(response, returnType);
_this.logger.debug(`Response data: ${JSON.stringify(data)}`);

response = _this.translateResponse(response);

callback(null, data, response);
}

// Return data for Promise-based usage
return {
data: data,
response: response
};
})
.catch(function(error) {

// Create a simple error object with descriptive message
const errorMsg = `Failed to decrypt response: ${error.message}`;

if (callback) {
callback(new Error(errorMsg), null, null);
}

// Reject the promise for Promise-based usage
return Promise.reject(new Error(errorMsg));
});
}).catch(function(error, response) {
source.cancel('Stream ended.');
var userError = {};
Expand Down
4 changes: 2 additions & 2 deletions src/authentication/core/Authorization.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var ApiException = require('../util/ApiException');
* This function calls for the generation of Signature message depending on the authentication type.
*
*/
exports.getToken = function(merchantConfig, logger){
exports.getToken = function(merchantConfig, isResponseMLEForApi, logger){

var authenticationType = merchantConfig.getAuthenticationType().toLowerCase();
var httpSigToken;
Expand All @@ -22,7 +22,7 @@ exports.getToken = function(merchantConfig, logger){
return httpSigToken;
}
else if(authenticationType === Constants.JWT) {
jwtSingToken = JWTSigToken.getToken(merchantConfig, logger);
jwtSingToken = JWTSigToken.getToken(merchantConfig, isResponseMLEForApi, logger);
return jwtSingToken;
}
else if(authenticationType === Constants.OAUTH) {
Expand Down
Loading