203 lines
3.8 KiB
JavaScript
203 lines
3.8 KiB
JavaScript
![]() |
|
||
|
/*!
|
||
|
* knox - auth
|
||
|
* Copyright(c) 2010 LearnBoost <dev@learnboost.com>
|
||
|
* MIT Licensed
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var crypto = require('crypto')
|
||
|
, parse = require('url').parse
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Valid keys.
|
||
|
*/
|
||
|
|
||
|
var keys =
|
||
|
[ 'acl'
|
||
|
, 'location'
|
||
|
, 'logging'
|
||
|
, 'notification'
|
||
|
, 'partNumber'
|
||
|
, 'policy'
|
||
|
, 'requestPayment'
|
||
|
, 'torrent'
|
||
|
, 'uploadId'
|
||
|
, 'uploads'
|
||
|
, 'versionId'
|
||
|
, 'versioning'
|
||
|
, 'versions'
|
||
|
, 'website'
|
||
|
]
|
||
|
|
||
|
/**
|
||
|
* Return an "Authorization" header value with the given `options`
|
||
|
* in the form of "AWS <key>:<signature>"
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function authorization (options) {
|
||
|
return 'AWS ' + options.key + ':' + sign(options)
|
||
|
}
|
||
|
|
||
|
module.exports = authorization
|
||
|
module.exports.authorization = authorization
|
||
|
|
||
|
/**
|
||
|
* Simple HMAC-SHA1 Wrapper
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function hmacSha1 (options) {
|
||
|
return crypto.createHmac('sha1', options.secret).update(options.message).digest('base64')
|
||
|
}
|
||
|
|
||
|
module.exports.hmacSha1 = hmacSha1
|
||
|
|
||
|
/**
|
||
|
* Create a base64 sha1 HMAC for `options`.
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function sign (options) {
|
||
|
options.message = stringToSign(options)
|
||
|
return hmacSha1(options)
|
||
|
}
|
||
|
module.exports.sign = sign
|
||
|
|
||
|
/**
|
||
|
* Create a base64 sha1 HMAC for `options`.
|
||
|
*
|
||
|
* Specifically to be used with S3 presigned URLs
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function signQuery (options) {
|
||
|
options.message = queryStringToSign(options)
|
||
|
return hmacSha1(options)
|
||
|
}
|
||
|
module.exports.signQuery= signQuery
|
||
|
|
||
|
/**
|
||
|
* Return a string for sign() with the given `options`.
|
||
|
*
|
||
|
* Spec:
|
||
|
*
|
||
|
* <verb>\n
|
||
|
* <md5>\n
|
||
|
* <content-type>\n
|
||
|
* <date>\n
|
||
|
* [headers\n]
|
||
|
* <resource>
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function stringToSign (options) {
|
||
|
var headers = options.amazonHeaders || ''
|
||
|
if (headers) headers += '\n'
|
||
|
var r =
|
||
|
[ options.verb
|
||
|
, options.md5
|
||
|
, options.contentType
|
||
|
, options.date ? options.date.toUTCString() : ''
|
||
|
, headers + options.resource
|
||
|
]
|
||
|
return r.join('\n')
|
||
|
}
|
||
|
module.exports.queryStringToSign = stringToSign
|
||
|
|
||
|
/**
|
||
|
* Return a string for sign() with the given `options`, but is meant exclusively
|
||
|
* for S3 presigned URLs
|
||
|
*
|
||
|
* Spec:
|
||
|
*
|
||
|
* <date>\n
|
||
|
* <resource>
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function queryStringToSign (options){
|
||
|
return 'GET\n\n\n' + options.date + '\n' + options.resource
|
||
|
}
|
||
|
module.exports.queryStringToSign = queryStringToSign
|
||
|
|
||
|
/**
|
||
|
* Perform the following:
|
||
|
*
|
||
|
* - ignore non-amazon headers
|
||
|
* - lowercase fields
|
||
|
* - sort lexicographically
|
||
|
* - trim whitespace between ":"
|
||
|
* - join with newline
|
||
|
*
|
||
|
* @param {Object} headers
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function canonicalizeHeaders (headers) {
|
||
|
var buf = []
|
||
|
, fields = Object.keys(headers)
|
||
|
;
|
||
|
for (var i = 0, len = fields.length; i < len; ++i) {
|
||
|
var field = fields[i]
|
||
|
, val = headers[field]
|
||
|
, field = field.toLowerCase()
|
||
|
;
|
||
|
if (0 !== field.indexOf('x-amz')) continue
|
||
|
buf.push(field + ':' + val)
|
||
|
}
|
||
|
return buf.sort().join('\n')
|
||
|
}
|
||
|
module.exports.canonicalizeHeaders = canonicalizeHeaders
|
||
|
|
||
|
/**
|
||
|
* Perform the following:
|
||
|
*
|
||
|
* - ignore non sub-resources
|
||
|
* - sort lexicographically
|
||
|
*
|
||
|
* @param {String} resource
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function canonicalizeResource (resource) {
|
||
|
var url = parse(resource, true)
|
||
|
, path = url.pathname
|
||
|
, buf = []
|
||
|
;
|
||
|
|
||
|
Object.keys(url.query).forEach(function(key){
|
||
|
if (!~keys.indexOf(key)) return
|
||
|
var val = '' == url.query[key] ? '' : '=' + encodeURIComponent(url.query[key])
|
||
|
buf.push(key + val)
|
||
|
})
|
||
|
|
||
|
return path + (buf.length ? '?' + buf.sort().join('&') : '')
|
||
|
}
|
||
|
module.exports.canonicalizeResource = canonicalizeResource
|