/*
* Copyright (c) 2013 - 2024 MasterCard International Incorporated
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* Neither the name of the MasterCard International Incorporated nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/**
* This module exposes the different domain
* object on which different API calls can be
* invoked upon.
*
* @class SimplifyJWS
* @static
*/
var SimplifyJWS = {},
SimplifyError = require('./error'),
simplifyUtils = require('./simplifyUtils'),
jws = require("jws");
/**
* An object containing the JWS header properties.
*
* @property HEADERS
* @type {Object}
*/
SimplifyJWS.HEADERS = {
URI: "api.simplifycommerce.com/uri",
TIMESTAMP: "api.simplifycommerce.com/timestamp",
NONCE: "api.simplifycommerce.com/nonce",
TOKEN: "api.simplifycommerce.com/token",
ALGORITHM: "HS256",
TYPE: "JWS",
MAX_TIMESTAMP_DIFF: (1000 * 60 * 5)
};
/**
* Function to get the JWS Signature for our API call.
*
* @method getJWSSignature
* @param {Object} auth An object containing the public & private API keys
* @param {Object} params The request paramaters to be used in the payload
* @param {String} uri The JSON Web Signature to decode
* @return {Object} Returns a JWS object
*/
SimplifyJWS.getJWSSignature = function(auth, params, uri) {
var jwsHeader = {
"typ": SimplifyJWS.HEADERS.TYPE,
"alg": SimplifyJWS.HEADERS.ALGORITHM,
"kid": auth.publicKey
};
if (!simplifyUtils.isUndefined(params.accessToken)) {
jwsHeader[SimplifyJWS.HEADERS.TOKEN] = params.accessToken;
}
jwsHeader[SimplifyJWS.HEADERS.URI] = uri;
jwsHeader[SimplifyJWS.HEADERS.TIMESTAMP] = new Date().getTime().toString();
jwsHeader[SimplifyJWS.HEADERS.NONCE] = simplifyUtils.generateRandomNumber();
// Private key is already BASE64 encoded, so need to decode it before
// it gets re-encoded as part of the JWS signing process
var privateKey = Buffer.from(auth.privateKey, 'base64');
// User node js 'jws' module to generate the JWS signature
return jws.sign({
header: jwsHeader,
payload: params,
secret: privateKey
});
};
/**
* Function to decode a JWS signature.
*
* @method decodeSignature
* @param {String} signature The JSON Web Signature to decode
* @return {Object} Returns a decoded JWS object
*/
SimplifyJWS.decodeSignature = function(signature) {
return jws.decode(signature);
};
/**
* Function to check if a JWS header is valid or not.
*
* @method isJWSHeaderValid
* @param {String} header The JSON Web Signature header
* @param {String} publicKey The user's public API key
* @param {String} url The url which was used as a header in the JWS object
* @param {Function} callback A callback function to handle an errors
* @return {Boolean} Returns true if the JWS header values are set correctly
*/
SimplifyJWS.isJWSHeaderValid = function(header, publicKey, url, callback) {
// Check the signing algorithm
if (header.alg !== SimplifyJWS.HEADERS.ALGORITHM) {
callback(new SimplifyError.JWS('Incorrect JWS algorithm found. HS256 is required'), null);
return false;
}
// Check that the correct number of header fields exists
if (Object.keys(header).length !== 7) {
callback(new SimplifyError.JWS('Incorrect number of header parameters found'), null);
return false;
}
// Check that a 'kid' header exists
if (header.typ !== SimplifyJWS.HEADERS.TYPE) {
callback(new SimplifyError.JWS('Incorrect JWS type found'), null);
return false;
}
// Check that a 'kid' header exists
if (simplifyUtils.isUndefined(header.kid)) {
callback(new SimplifyError.JWS('Missing Key ID'), null);
return false;
}
// Check that the key matches and is live
if (header.kid !== publicKey) {
if (!simplifyUtils.isLiveKey(publicKey)) {
callback(new SimplifyError.JWS('Incorrect Key ID'), null);
return false;
}
}
// Check that a 'uri' header exists
if (simplifyUtils.isUndefined(SimplifyJWS.HEADERS.URI)) {
callback(new SimplifyError.JWS('Missing URI'), null);
return false;
}
// Check that a 'uri' header exists
if (header[SimplifyJWS.HEADERS.URI] !== url) {
callback(new SimplifyError.JWS('Invalid URI found'), null);
return false;
}
// Check that a 'uname' header exists
if (simplifyUtils.isUndefined(header.uname)) {
callback(new SimplifyError.JWS('Missing usename'), null);
return false;
}
// Check that a 'nonce' header exists
if (simplifyUtils.isUndefined(SimplifyJWS.HEADERS.NONCE)) {
callback(new SimplifyError.JWS('Missing nonce'), null);
return false;
}
// Check that a 'timestamp' header exists
if (simplifyUtils.isUndefined(SimplifyJWS.HEADERS.TIMESTAMP)) {
callback(new SimplifyError.JWS('Missing timestamp'), null);
return false;
}
// Check that a 'timestamp' header is valid (not older than 5 mins)
if (new Date().getTime() - header[SimplifyJWS.HEADERS.TIMESTAMP] > SimplifyJWS.HEADERS.MAX_TIMESTAMP_DIFF) {
callback(new SimplifyError.JWS('Timestamp expired'), null);
return false;
}
return true;
};
// Export our module
module.exports = SimplifyJWS;