PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/amazon-fps-cbui-pipeline.js

https://github.com/rupa/amazon-fps
JavaScript | 267 lines | 166 code | 32 blank | 69 comment | 25 complexity | 7530123267c3ce2cbf2b8a1d22c6a99b MD5 | raw file
  1. (function(){
  2. var crypto = require('crypto');
  3. var url = require('url');
  4. var SIGNATURE_KEYNAME = "signature";
  5. var SIGNATURE_METHOD_KEYNAME = "signatureMethod";
  6. var SIGNATURE_VERSION_KEYNAME = "signatureVersion";
  7. var HMAC_SHA1_ALGORITHM = "HmacSHA1";
  8. var HMAC_SHA256_ALGORITHM = "HmacSHA256";
  9. var HTTP_GET_METHOD = "GET";
  10. var MANDATORY_PARAMS = {
  11. all: ["pipelineName","version","returnURL","callerReference"],
  12. SingleUse: ["transactionAmount"],
  13. Recurring: ["transactionAmount","recurringPeriod"],
  14. Recipient: ["maxFixedFee","maxVariableFee","recipientPaysFee"],
  15. MultiUse: ["globalAmountLimit"], //TODO there are some other requirements here that should be delt with (usagelimit types, etc)
  16. EditToken: ["tokenId"]
  17. };
  18. module.exports = function(pipelineName, awsAccessKey, awsSecretKey){
  19. /**
  20. * Version parameter for consistent signature for incoming and outgoing requests
  21. */
  22. var VERSION = "2009-01-09";
  23. var SIGNATURE_VERSION = 2;
  24. var SIGNATURE_METHOD = HMAC_SHA256_ALGORITHM;
  25. var CBUI_URL = "https://authorize.payments.amazon.com/cobranded-ui/actions/start";
  26. var parameters = {};
  27. parameters["callerKey"] = awsAccessKey;
  28. parameters["pipelineName"] = pipelineName;
  29. parameters["version"] = VERSION;
  30. parameters["signatureVersion"] = SIGNATURE_VERSION;
  31. parameters["signatureMethod"] = SIGNATURE_METHOD;
  32. this.addParameter = function(key, value){
  33. parameters[key] = value;
  34. };
  35. this.addOptionalParameters = function(params) {
  36. Object.keys(params).forEach(function(key){
  37. parameters[key] = params[key];
  38. });
  39. }
  40. /**
  41. * Constructs the query string for the parameters added to this class
  42. *
  43. * This function also calculates the signature of the all the name value pairs
  44. * added to the class.
  45. *
  46. * @return string URL
  47. */
  48. this.getURL = function() {
  49. validateParameters('all',parameters);
  50. validateParameters(parameters.pipelineName,parameters);
  51. return constructUrl(parameters);
  52. }
  53. /**
  54. * Computes RFC 2104-compliant HMAC signature for request parameters
  55. * Implements AWS Signature, as per following spec:
  56. *
  57. * If Signature Version is 1, it performs the following:
  58. *
  59. * Sorts all parameters (including SignatureVersion and excluding Signature,
  60. * the value of which is being created), ignoring case.
  61. *
  62. * Iterate over the sorted list and append the parameter name (in original case)
  63. * and then its value. It will not URL-encode the parameter values before
  64. * constructing this string. There are no separators.
  65. *
  66. * If Signature Version is 2, string to sign is based on following:
  67. *
  68. * 1. The HTTP Request Method followed by an ASCII newline (%0A)
  69. * 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline.
  70. * 3. The URL encoded HTTP absolute path component of the URI
  71. * (up to but not including the query string parameters);
  72. * if this is empty use a forward '/'. This parameter is followed by an ASCII newline.
  73. * 4. The concatenation of all query string components (names and values)
  74. * as UTF-8 characters which are URL encoded as per RFC 3986
  75. * (hex characters MUST be uppercase), sorted using lexicographic byte ordering.
  76. * Parameter names are separated from their values by the '=' character
  77. * (ASCII character 61), even if the value is empty.
  78. * Pairs of parameter and values are separated by the '&' character (ASCII code 38).
  79. *
  80. */
  81. signParameters = function(parameters, httpMethod, host, requestURI){
  82. var signatureVersion = parameters[SIGNATURE_VERSION_KEYNAME];
  83. var algorithm = HMAC_SHA1_ALGORITHM;
  84. var stringToSign = null;
  85. if (2 === signatureVersion) {
  86. algorithm = parameters[SIGNATURE_METHOD_KEYNAME];
  87. stringToSign = calculateStringToSignV2(parameters, httpMethod, host, requestURI);
  88. } else {
  89. stringToSign = calculateStringToSignV1(parameters);
  90. }
  91. return sign(stringToSign, awsSecretKey, algorithm);
  92. }
  93. /**
  94. * Calculate String to Sign for SignatureVersion 1
  95. * @param array $parameters request parameters
  96. * @return String to Sign
  97. */
  98. var calculateStringToSignV1 = function(parameters) {
  99. var data = '';
  100. var parameters = makeSortedObject(parameters,false);
  101. parameters.forEach(function(param){
  102. data += (param['key'] + param['value']);
  103. });
  104. return data;
  105. }
  106. /**
  107. * Calculate String to Sign for SignatureVersion 2
  108. * @param array $parameters request parameters
  109. * @return String to Sign
  110. */
  111. var calculateStringToSignV2 = function(parameters, httpMethod, hostHeader, requestURI) {
  112. if (!httpMethod) {
  113. throw "HttpMethod cannot be null";
  114. }
  115. var data = httpMethod;
  116. data += "\n";
  117. if (!hostHeader) {
  118. hostHeader = "";
  119. }
  120. data += hostHeader;
  121. data += "\n";
  122. if (!requestURI) {
  123. requestURI = "/";
  124. }
  125. var uriencoded = requestURI.split('/').map(escape).join('/');
  126. data += uriencoded;
  127. data += "\n";
  128. parameters = makeSortedObject(parameters,false); //uksort($parameters, 'strcmp');
  129. data += getParametersAsString(parameters);
  130. return data;
  131. }
  132. /**
  133. * Computes RFC 2104-compliant HMAC signature.
  134. */
  135. function sign(data, key, algorithm) {
  136. var hmac;
  137. if (algorithm === HMAC_SHA1_ALGORITHM) {
  138. hmac = crypto.createHmac('sha1', key);
  139. } else if (algorithm === HMAC_SHA256_ALGORITHM) {
  140. hmac = crypto.createHmac('sha256', key);
  141. } else {
  142. throw "Non-supported signing method specified";
  143. }
  144. hmac.update(data);
  145. return hmac.digest('base64');
  146. //php: l1aQhiVCfR2L0Q9t1Nt1HRa7tF0= <== Yes this sign is identical to the php version
  147. //node:l1aQhiVCfR2L0Q9t1Nt1HRa7tF0=
  148. }
  149. /**
  150. * Construct the pipeline request url using given parameters.
  151. * Computes signature and adds it as additional parameter.
  152. * @param parameters - Map of pipeline request parameters.
  153. * @return Returns the pipeline request url.
  154. * @throws MalformedURLException
  155. * @throws SignatureException
  156. * @throws UnsupportedEncodingException
  157. */
  158. function constructUrl($parameters) {
  159. if(!parameters){
  160. throw "Parameters can not be empty.";
  161. }
  162. var hostHeader = getHostHeader(CBUI_URL);
  163. var requestURI = getRequestURI(CBUI_URL);
  164. var signature = signParameters(parameters, HTTP_GET_METHOD, hostHeader, requestURI);
  165. parameters["signature"] = signature;
  166. return CBUI_URL + "?" + buildQueryString(parameters);
  167. }
  168. function getHostHeader(endPoint) {
  169. var theurl = url.parse(endPoint);
  170. var host = theurl.host.toLowerCase();
  171. var protocol = theurl.protocol.toUpperCase();
  172. if(theurl.hasOwnProperty('port')) {
  173. if (("HTTPS" == protocol && theurl.port != 443) || ("HTTP" == protocol && theurl.port != 80)) {
  174. return host + ":" + theurl.port;
  175. }
  176. }
  177. return host;
  178. }
  179. function getRequestURI(endPoint) {
  180. var theurl = url.parse(endPoint);
  181. var requestURI = '/';
  182. if(theurl.hasOwnProperty('pathname')){
  183. requestURI = theurl.pathname;
  184. }
  185. return requestURI;
  186. }
  187. function validateCommonMandatoryParameters(parameters) {
  188. if (!parameters.hasOwnProperty("pipelineName")){
  189. throw "pipelineName is missing in parameters.";
  190. }
  191. if (!parameters.hasOwnProperty("version")){
  192. throw "version is missing in parameters.";
  193. }
  194. if (!parameters.hasOwnProperty("returnURL")){
  195. throw "returnURL is missing in parameters.";
  196. }
  197. if (!parameters.hasOwnProperty("callerReference")){
  198. throw "callerReference is missing in parameters.";
  199. }
  200. }
  201. function validateParameters(type, parameters){
  202. MANDATORY_PARAMS[type].forEach(function(param){
  203. if (!parameters.hasOwnProperty(param)){
  204. throw param + " is missing from the parameters. This parameter is required for " + type;
  205. }
  206. });
  207. }
  208. //////////////////////////////////////////////
  209. // UTILS //
  210. //////////////////////////////////////////////
  211. function urlEncode(toencode){
  212. return escape(toencode).replace(/\//g,'%2F').replace(/\+/g, '%2B');
  213. }
  214. function buildQueryString(params){
  215. return Object.keys(params).map(function(p){
  216. return escape(p) + "=" + urlEncode(params[p]);
  217. }).join('&');
  218. }
  219. /**
  220. * Convert paremeters to Url encoded query string
  221. */
  222. function getParametersAsString(parameters) {
  223. return parameters.map(function(param){
  224. return (param.key + '=' + urlEncode(param.value));
  225. }).join('&');
  226. }
  227. function makeSortedObject(obj, casesensitive){
  228. return Object.keys(obj).map(function(key){
  229. return {key: key, value: obj[key]};
  230. }).sort(function(a,b){
  231. return (casesensitive? a.key : a.key.toLowerCase()) > (casesensitive? b.key : b.key.toLowerCase());
  232. });
  233. }
  234. }//end of the class
  235. })();