211 lines
7.2 KiB
JavaScript
211 lines
7.2 KiB
JavaScript
var AWS = require('../core'),
|
|
url = AWS.util.url,
|
|
crypto = AWS.util.crypto.lib,
|
|
base64Encode = AWS.util.base64.encode,
|
|
inherit = AWS.util.inherit;
|
|
|
|
var queryEncode = function (string) {
|
|
var replacements = {
|
|
'+': '-',
|
|
'=': '_',
|
|
'/': '~'
|
|
};
|
|
return string.replace(/[\+=\/]/g, function (match) {
|
|
return replacements[match];
|
|
});
|
|
};
|
|
|
|
var signPolicy = function (policy, privateKey) {
|
|
var sign = crypto.createSign('RSA-SHA1');
|
|
sign.write(policy);
|
|
return queryEncode(sign.sign(privateKey, 'base64'));
|
|
};
|
|
|
|
var signWithCannedPolicy = function (url, expires, keyPairId, privateKey) {
|
|
var policy = JSON.stringify({
|
|
Statement: [
|
|
{
|
|
Resource: url,
|
|
Condition: { DateLessThan: { 'AWS:EpochTime': expires } }
|
|
}
|
|
]
|
|
});
|
|
|
|
return {
|
|
Expires: expires,
|
|
'Key-Pair-Id': keyPairId,
|
|
Signature: signPolicy(policy.toString(), privateKey)
|
|
};
|
|
};
|
|
|
|
var signWithCustomPolicy = function (policy, keyPairId, privateKey) {
|
|
policy = policy.replace(/\s/mg, '');
|
|
|
|
return {
|
|
Policy: queryEncode(base64Encode(policy)),
|
|
'Key-Pair-Id': keyPairId,
|
|
Signature: signPolicy(policy, privateKey)
|
|
};
|
|
};
|
|
|
|
var determineScheme = function (url) {
|
|
var parts = url.split('://');
|
|
if (parts.length < 2) {
|
|
throw new Error('Invalid URL.');
|
|
}
|
|
|
|
return parts[0].replace('*', '');
|
|
};
|
|
|
|
var getRtmpUrl = function (rtmpUrl) {
|
|
var parsed = url.parse(rtmpUrl);
|
|
return parsed.path.replace(/^\//, '') + (parsed.hash || '');
|
|
};
|
|
|
|
var getResource = function (url) {
|
|
switch (determineScheme(url)) {
|
|
case 'http':
|
|
case 'https':
|
|
return url;
|
|
case 'rtmp':
|
|
return getRtmpUrl(url);
|
|
default:
|
|
throw new Error('Invalid URI scheme. Scheme must be one of'
|
|
+ ' http, https, or rtmp');
|
|
}
|
|
};
|
|
|
|
var handleError = function (err, callback) {
|
|
if (!callback || typeof callback !== 'function') {
|
|
throw err;
|
|
}
|
|
|
|
callback(err);
|
|
};
|
|
|
|
var handleSuccess = function (result, callback) {
|
|
if (!callback || typeof callback !== 'function') {
|
|
return result;
|
|
}
|
|
|
|
callback(null, result);
|
|
};
|
|
|
|
AWS.CloudFront.Signer = inherit({
|
|
/**
|
|
* A signer object can be used to generate signed URLs and cookies for granting
|
|
* access to content on restricted CloudFront distributions.
|
|
*
|
|
* @see http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
|
|
*
|
|
* @param keyPairId [String] (Required) The ID of the CloudFront key pair
|
|
* being used.
|
|
* @param privateKey [String] (Required) A private key in RSA format.
|
|
*/
|
|
constructor: function Signer(keyPairId, privateKey) {
|
|
if (keyPairId === void 0 || privateKey === void 0) {
|
|
throw new Error('A key pair ID and private key are required');
|
|
}
|
|
|
|
this.keyPairId = keyPairId;
|
|
this.privateKey = privateKey;
|
|
},
|
|
|
|
/**
|
|
* Create a signed Amazon CloudFront Cookie.
|
|
*
|
|
* @param options [Object] The options to create a signed cookie.
|
|
* @option options url [String] The URL to which the signature will grant
|
|
* access. Required unless you pass in a full
|
|
* policy.
|
|
* @option options expires [Number] A Unix UTC timestamp indicating when the
|
|
* signature should expire. Required unless you
|
|
* pass in a full policy.
|
|
* @option options policy [String] A CloudFront JSON policy. Required unless
|
|
* you pass in a url and an expiry time.
|
|
*
|
|
* @param cb [Function] if a callback is provided, this function will
|
|
* pass the hash as the second parameter (after the error parameter) to
|
|
* the callback function.
|
|
*
|
|
* @return [Object] if called synchronously (with no callback), returns the
|
|
* signed cookie parameters.
|
|
* @return [null] nothing is returned if a callback is provided.
|
|
*/
|
|
getSignedCookie: function (options, cb) {
|
|
var signatureHash = 'policy' in options
|
|
? signWithCustomPolicy(options.policy, this.keyPairId, this.privateKey)
|
|
: signWithCannedPolicy(options.url, options.expires, this.keyPairId, this.privateKey);
|
|
|
|
var cookieHash = {};
|
|
for (var key in signatureHash) {
|
|
if (Object.prototype.hasOwnProperty.call(signatureHash, key)) {
|
|
cookieHash['CloudFront-' + key] = signatureHash[key];
|
|
}
|
|
}
|
|
|
|
return handleSuccess(cookieHash, cb);
|
|
},
|
|
|
|
/**
|
|
* Create a signed Amazon CloudFront URL.
|
|
*
|
|
* Keep in mind that URLs meant for use in media/flash players may have
|
|
* different requirements for URL formats (e.g. some require that the
|
|
* extension be removed, some require the file name to be prefixed
|
|
* - mp4:<path>, some require you to add "/cfx/st" into your URL).
|
|
*
|
|
* @param options [Object] The options to create a signed URL.
|
|
* @option options url [String] The URL to which the signature will grant
|
|
* access. Any query params included with
|
|
* the URL should be encoded. Required.
|
|
* @option options expires [Number] A Unix UTC timestamp indicating when the
|
|
* signature should expire. Required unless you
|
|
* pass in a full policy.
|
|
* @option options policy [String] A CloudFront JSON policy. Required unless
|
|
* you pass in a url and an expiry time.
|
|
*
|
|
* @param cb [Function] if a callback is provided, this function will
|
|
* pass the URL as the second parameter (after the error parameter) to
|
|
* the callback function.
|
|
*
|
|
* @return [String] if called synchronously (with no callback), returns the
|
|
* signed URL.
|
|
* @return [null] nothing is returned if a callback is provided.
|
|
*/
|
|
getSignedUrl: function (options, cb) {
|
|
try {
|
|
var resource = getResource(options.url);
|
|
} catch (err) {
|
|
return handleError(err, cb);
|
|
}
|
|
|
|
var parsedUrl = url.parse(options.url, true),
|
|
signatureHash = Object.prototype.hasOwnProperty.call(options, 'policy')
|
|
? signWithCustomPolicy(options.policy, this.keyPairId, this.privateKey)
|
|
: signWithCannedPolicy(resource, options.expires, this.keyPairId, this.privateKey);
|
|
|
|
parsedUrl.search = null;
|
|
for (var key in signatureHash) {
|
|
if (Object.prototype.hasOwnProperty.call(signatureHash, key)) {
|
|
parsedUrl.query[key] = signatureHash[key];
|
|
}
|
|
}
|
|
|
|
try {
|
|
var signedUrl = determineScheme(options.url) === 'rtmp'
|
|
? getRtmpUrl(url.format(parsedUrl))
|
|
: url.format(parsedUrl);
|
|
} catch (err) {
|
|
return handleError(err, cb);
|
|
}
|
|
|
|
return handleSuccess(signedUrl, cb);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
module.exports = AWS.CloudFront.Signer;
|