/**
|
* Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
* the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations under the License.
|
*
|
* @file src/auth.js
|
* @author leeight
|
*/
|
|
/* eslint-env node */
|
/* eslint max-params:[0,10] */
|
|
var util = require('util');
|
var u = require('underscore');
|
|
var debug = require('debug')('bce-sdk:auth');
|
|
var H = require('./headers');
|
var strings = require('./strings');
|
|
/**
|
* Auth
|
*
|
* @constructor
|
* @param {string} ak The access key.
|
* @param {string} sk The security key.
|
*/
|
function Auth(ak, sk) {
|
this.ak = ak;
|
this.sk = sk;
|
}
|
|
/**
|
* Generate the signature based on http://gollum.baidu.com/AuthenticationMechanism
|
*
|
* @param {string} method The http request method, such as GET, POST, DELETE, PUT, ...
|
* @param {string} resource The request path.
|
* @param {Object=} params The query strings.
|
* @param {Object=} headers The http request headers.
|
* @param {number=} timestamp Set the current timestamp.
|
* @param {number=} expirationInSeconds The signature validation time.
|
* @param {Array.<string>=} headersToSign The request headers list which will be used to calcualate the signature.
|
*
|
* @return {string} The signature.
|
*/
|
Auth.prototype.generateAuthorization = function (method, resource, params,
|
headers, timestamp, expirationInSeconds, headersToSign) {
|
|
var now = timestamp ? new Date(timestamp * 1000) : new Date();
|
var rawSessionKey = util.format('bce-auth-v1/%s/%s/%d',
|
this.ak, now.toISOString().replace(/\.\d+Z$/, 'Z'), expirationInSeconds || 1800);
|
debug('rawSessionKey = %j', rawSessionKey);
|
var sessionKey = this.hash(rawSessionKey, this.sk);
|
|
var canonicalUri = this.uriCanonicalization(resource);
|
var canonicalQueryString = this.queryStringCanonicalization(params || {});
|
|
var rv = this.headersCanonicalization(headers || {}, headersToSign);
|
var canonicalHeaders = rv[0];
|
var signedHeaders = rv[1];
|
debug('canonicalUri = %j', canonicalUri);
|
debug('canonicalQueryString = %j', canonicalQueryString);
|
debug('canonicalHeaders = %j', canonicalHeaders);
|
debug('signedHeaders = %j', signedHeaders);
|
|
var rawSignature = util.format('%s\n%s\n%s\n%s',
|
method, canonicalUri, canonicalQueryString, canonicalHeaders);
|
debug('rawSignature = %j', rawSignature);
|
debug('sessionKey = %j', sessionKey);
|
var signature = this.hash(rawSignature, sessionKey);
|
|
if (signedHeaders.length) {
|
return util.format('%s/%s/%s', rawSessionKey, signedHeaders.join(';'), signature);
|
}
|
|
return util.format('%s//%s', rawSessionKey, signature);
|
};
|
|
Auth.prototype.uriCanonicalization = function (uri) {
|
return uri;
|
};
|
|
/**
|
* Canonical the query strings.
|
*
|
* @see http://gollum.baidu.com/AuthenticationMechanism#生成CanonicalQueryString
|
* @param {Object} params The query strings.
|
* @return {string}
|
*/
|
Auth.prototype.queryStringCanonicalization = function (params) {
|
var canonicalQueryString = [];
|
Object.keys(params).forEach(function (key) {
|
if (key.toLowerCase() === H.AUTHORIZATION.toLowerCase()) {
|
return;
|
}
|
|
var value = params[key] == null ? '' : params[key];
|
canonicalQueryString.push(key + '=' + strings.normalize(value));
|
});
|
|
canonicalQueryString.sort();
|
|
return canonicalQueryString.join('&');
|
};
|
|
/**
|
* Canonical the http request headers.
|
*
|
* @see http://gollum.baidu.com/AuthenticationMechanism#生成CanonicalHeaders
|
* @param {Object} headers The http request headers.
|
* @param {Array.<string>=} headersToSign The request headers list which will be used to calcualate the signature.
|
* @return {*} canonicalHeaders and signedHeaders
|
*/
|
Auth.prototype.headersCanonicalization = function (headers, headersToSign) {
|
if (!headersToSign || !headersToSign.length) {
|
headersToSign = [H.HOST, H.CONTENT_MD5, H.CONTENT_LENGTH, H.CONTENT_TYPE];
|
}
|
debug('headers = %j, headersToSign = %j', headers, headersToSign);
|
|
var headersMap = {};
|
headersToSign.forEach(function (item) {
|
headersMap[item.toLowerCase()] = true;
|
});
|
|
var canonicalHeaders = [];
|
Object.keys(headers).forEach(function (key) {
|
var value = headers[key];
|
value = u.isString(value) ? strings.trim(value) : value;
|
if (value == null || value === '') {
|
return;
|
}
|
key = key.toLowerCase();
|
if (/^x\-bce\-/.test(key) || headersMap[key] === true) {
|
canonicalHeaders.push(util.format('%s:%s',
|
// encodeURIComponent(key), encodeURIComponent(value)));
|
strings.normalize(key), strings.normalize(value)));
|
}
|
});
|
|
canonicalHeaders.sort();
|
|
var signedHeaders = [];
|
canonicalHeaders.forEach(function (item) {
|
signedHeaders.push(item.split(':')[0]);
|
});
|
|
return [canonicalHeaders.join('\n'), signedHeaders];
|
};
|
|
Auth.prototype.hash = function (data, key) {
|
var crypto = require('crypto');
|
var sha256Hmac = crypto.createHmac('sha256', key);
|
sha256Hmac.update(data);
|
return sha256Hmac.digest('hex');
|
};
|
|
module.exports = Auth;
|