From 1227354f217ca87d5e9f84e0c51ba6c71e0c7438 Mon Sep 17 00:00:00 2001 From: Ion Caliman Date: Fri, 27 Mar 2015 14:58:38 +0200 Subject: [PATCH] first commit --- .gitignore | 5 ++ README.md | 39 +++++++++++++ aws-sign.js | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 aws-sign.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e758777 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store + +**/node_modules + +.idea \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d8e334 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# aws-sign + +Simple module to calculate `Authorization` header for Amazon AWS REST requests, for Parse.com. + + +## Usage + +1. Put 'aws-sign.js' file into your 'cloud/modules/' folder. +2. Require module in Cloud Code. + +# Examples + +```js +var AwsSign = require('cloud/modules/aws-sign.js'); +var signer = new AwsSign({ + accessKeyId: 'AKIAIOSFODNN7EXAMPLE', + secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' +}); + +var opts = { + method: 'PUT', + url: 'http://johnsmith.s3.amazonaws.com/photos/puppy.jpg', + headers: { ... }, + ... // Other request options, ignored by AwsSign. + body: {} + success: function() {}, + error: function() {} +}; +signer.sign(opts); + +Parse.Cloud.httpRequest(opts); +``` + +The following keys are mandatory: + +* `method` +* `url` + +Others are optional. A date header (`headers.date`) will be added for you if it is not already set. \ No newline at end of file diff --git a/aws-sign.js b/aws-sign.js new file mode 100644 index 0000000..f9760f6 --- /dev/null +++ b/aws-sign.js @@ -0,0 +1,158 @@ +var + crypto = require('crypto'), + url = require('url'), + querystring = require('querystring'); + + +function AWSRestSigner(credentials) { + this.accessKeyId = credentials.accessKeyId; + this.secretAccessKey = credentials.secretAccessKey; + this.debug = false; +} + +AWSRestSigner.subResources = ['acl', 'lifecycle', 'location', 'logging', 'notification', 'partNumber', 'policy', 'requestPayment', 'torrent', 'uploadId', 'uploads', 'versionId', 'versioning', 'versions', 'website']; + +AWSRestSigner.prototype.canonizeAwzHeaders = function(xAmzHeaders) { + if (xAmzHeaders) { + var lcHeaders = {}; + Object.keys(xAmzHeaders).forEach(function(header) { + var h = header.toLowerCase(); + if (h!='x-amz-date') { + lcHeaders[h]=xAmzHeaders[header]; + } + }); + + return Object.keys(lcHeaders) + .map(function(header) { + return header.toLowerCase(); + }) + .sort() + .map(function(header) { + return header+':'+lcHeaders[header]+"\n"; + }) + .join(''); + } else { + return ''; + } +} + +AWSRestSigner.prototype.extractSubResources = function(queryString) { + var query = querystring.parse(queryString); + + var subresources = []; + Object.keys(query).forEach(function(param) { + if (AWSRestSigner.subResources.indexOf(param)>=0) { + subresources.push(param); + } + }); + + if (subresources.length) { + subresources = subresources.sort(); + var queryToSign = subresources.map(function(param) { + var result = param; + if (query[param]!='') { + result+="="+query[param]; + } + return result; + }); + return "?"+queryToSign.join("&") + } + + return ''; +} + +AWSRestSigner.prototype.sign = function(opts) { + var urlData = url.parse(opts.url); + var + method = opts.method, + host = urlData.hostname || '', + path = urlData.pathname, + xAmzHeaders = {}, + date, contentType, contentMd5, + bucket = ""; + + + + var _match = host.match(/^(.*)\.s3\.amazonaws\.com/); + if (_match) { + bucket = _match[1]; + } else { + bucket = host; + } + + if (!opts.headers) { + opts.headers = {}; + } + + Object.keys(opts.headers).forEach(function(key) { + var lcKey = key.toLowerCase(); + switch(lcKey) { + case "date": + date = opts.headers[key]; + break; + case "content-type": + contentType = opts.headers[key]; + break; + case "content-md5": + contentMd5 = opts.headers[key]; + break; + default: + if("x-amz-" === lcKey.slice(0, 6)) { + xAmzHeaders[lcKey] = opts.headers[key]; + } + break; + } + }); + + if (!date) { + date = new Date().toUTCString(); + opts.headers.date = date; + } + + opts.headers["Authorization"] = this._sign(method, bucket, path, date, contentType, contentMd5, xAmzHeaders); +} + + +AWSRestSigner.prototype._sign = function(method, bucket, path, date, contentType, contentMd5, xAmzHeaders) { + var qPos = path.indexOf('?'), queryToSign=''; + if (qPos>=0) { + var queryPart = path.substr(qPos+1, path.length); + path = path.substr(0,qPos); + queryToSign = this.extractSubResources(queryPart); + } + + var canonicalizedAmzHeaders = this.canonizeAwzHeaders(xAmzHeaders); + + var canonicalizedResource = ''; + if (bucket!='') { + canonicalizedResource += '/'+bucket; + } + canonicalizedResource += path + queryToSign; + + var stringToSign = method + "\n"; + if (contentMd5) { + stringToSign += contentMd5; + } + stringToSign += "\n"; + + if (contentType) { + stringToSign += contentType; + } + stringToSign += "\n"; + + stringToSign += + date + "\n" + + canonicalizedAmzHeaders + + canonicalizedResource; + + if (this.debug) { + console.log("-----------") + console.log(stringToSign.replace(/\n/g, "\\n\n")); + console.log("-----------") + } + + return 'AWS ' + this.accessKeyId + ':' + crypto.createHmac('sha1', this.secretAccessKey).update(stringToSign).digest('base64'); +} + +module.exports = AWSRestSigner; +