oauth-signature
Version:
OAuth 1.0a signature generator (RFC 5849)
296 lines (266 loc) • 8.56 kB
JavaScript
;(function() {
'use strict';
// Check the existence of module and module.exports to detect node
var isNode = (typeof module != 'undefined' && typeof module.exports != 'undefined');
function OAuthSignature() {
}
OAuthSignature.prototype.generate = function (httpMethod, url, parameters, consumerSecret, tokenSecret, options) {
var signatureBaseString = new SignatureBaseString(httpMethod, url, parameters).generate();
var encodeSignature = true;
if (options) {
encodeSignature = options.encodeSignature;
}
return new HmacSha1Signature(signatureBaseString, consumerSecret, tokenSecret).generate(encodeSignature);
};
// Specification: http://oauth.net/core/1.0/#anchor14
// url: if the scheme is missing, http will be added automatically
function SignatureBaseString(httpMethod, url, parameters) {
parameters = new ParametersLoader(parameters).get();
this._httpMethod = new HttpMethodElement(httpMethod).get();
this._url = new UrlElement(url).get();
this._parameters = new ParametersElement(parameters).get();
this._rfc3986 = new Rfc3986();
}
SignatureBaseString.prototype = {
generate : function () {
// HTTP_METHOD & url & parameters
return this._rfc3986.encode(this._httpMethod) + '&'
+ this._rfc3986.encode(this._url) + '&'
+ this._rfc3986.encode(this._parameters);
}
};
function HttpMethodElement(httpMethod) {
this._httpMethod = httpMethod || '';
}
HttpMethodElement.prototype = {
get : function () {
return this._httpMethod.toUpperCase();
}
};
function UrlElement(url) {
this._url = url || '';
}
UrlElement.prototype = {
get : function () {
// The following is to prevent js-url from loading the window.location
if (!this._url) {
return this._url;
}
// FIXME: Make this behaviour explicit by returning warnings
if (this._url.indexOf('://') == -1) {
this._url = 'http://' + this._url;
}
// Handle parsing the url in node or in browser
var parsedUrl = isNode ? this.parseInNode() : this.parseInBrowser(),
// FIXME: Make this behaviour explicit by returning warnings
scheme = (parsedUrl.scheme || 'http').toLowerCase(),
// FIXME: Make this behaviour explicit by returning warnings
authority = (parsedUrl.authority || '').toLocaleLowerCase(),
path = parsedUrl.path || '',
port = parsedUrl.port || '';
// FIXME: Make this behaviour explicit by returning warnings
if ((port == 80 && scheme == 'http')
|| (port == 443 && scheme == 'https'))
{
port = '';
}
var baseUrl = scheme + '://' + authority;
baseUrl = baseUrl + (!!port ? ':' + port : '');
// FIXME: Make this behaviour explicit by returning warnings
if (path == '/' && this._url.indexOf(baseUrl + path) === -1) {
path = '';
}
this._url =
(scheme ? scheme + '://' : '')
+ authority
+ (port ? ':' + port : '')
+ path;
return this._url;
},
parseInBrowser : function () {
return {
scheme : url('protocol', this._url).toLowerCase(),
authority : url('hostname', this._url).toLocaleLowerCase(),
port : url('port', this._url),
path :url('path', this._url)
};
},
parseInNode : function () {
var url = require('uri-js'),
parsedUri = url.parse(this._url),
scheme = parsedUri.scheme;
// strip the ':' at the end of the scheme added by the url module
if (scheme.charAt(scheme.length - 1) == ":") {
scheme = scheme.substring(0, scheme.length - 1);
}
return {
scheme : scheme,
authority : parsedUri.host,
port : parsedUri.port,
path : parsedUri.path
};
}
};
function ParametersElement (parameters) {
// Parameters format: { 'key': ['value 1', 'value 2'] };
this._parameters = parameters || {};
this._sortedKeys = [];
this._normalizedParameters = [];
this._rfc3986 = new Rfc3986();
this._sortParameters();
this._concatenateParameters();
}
ParametersElement.prototype = {
_sortParameters : function () {
var key,
encodedKey;
for (key in this._parameters) {
if (this._parameters.hasOwnProperty(key)) {
encodedKey = this._rfc3986.encode(key);
this._sortedKeys.push(encodedKey);
}
}
this._sortedKeys.sort();
},
_concatenateParameters : function () {
var i;
for (i = 0; i < this._sortedKeys.length; i++) {
this._normalizeParameter(this._sortedKeys[i]);
}
},
_normalizeParameter : function (encodedKey) {
var i,
key = this._rfc3986.decode(encodedKey),
values = this._parameters[key],
encodedValue;
values.sort();
for (i = 0; i < values.length; i++) {
encodedValue = this._rfc3986.encode(values[i]);
this._normalizedParameters.push(encodedKey + '=' + encodedValue)
}
},
get : function () {
return this._normalizedParameters.join('&');
}
};
function ParametersLoader (parameters) {
// Format: { 'key': ['value 1', 'value 2'] }
this._parameters = {};
this._loadParameters(parameters || {});
}
ParametersLoader.prototype = {
_loadParameters : function (parameters) {
if (parameters instanceof Array) {
this._loadParametersFromArray(parameters);
} else if (typeof parameters === 'object') {
this._loadParametersFromObject(parameters);
}
},
_loadParametersFromArray : function (parameters) {
var i;
for (i = 0; i < parameters.length; i++) {
this._loadParametersFromObject(parameters[i]);
}
},
_loadParametersFromObject : function (parameters) {
var key;
for (key in parameters) {
if (parameters.hasOwnProperty(key)) {
var stringValue = this._getStringFromParameter(parameters[key]);
this._loadParameterValue(key, stringValue);
}
}
},
_loadParameterValue : function (key, value) {
var i;
if (value instanceof Array) {
for (i = 0; i < value.length; i++) {
var stringValue = this._getStringFromParameter(value[i]);
this._addParameter(key, stringValue);
}
if (value.length == 0) {
this._addParameter(key, '');
}
} else {
this._addParameter(key, value);
}
},
_getStringFromParameter : function (parameter) {
var stringValue = parameter || '';
try {
if (typeof parameter === 'number' || typeof parameter === 'boolean') {
stringValue = parameter.toString();
}
} catch (e) {}
return stringValue;
},
_addParameter : function (key, value) {
if (!this._parameters[key]) {
this._parameters[key] = [];
}
this._parameters[key].push(value);
},
get : function () {
return this._parameters;
}
};
function Rfc3986() {
}
Rfc3986.prototype = {
encode : function (decoded) {
if (!decoded) {
return '';
}
// using implementation from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FencodeURIComponent
return encodeURIComponent(decoded)
.replace(/[!'()]/g, escape)
.replace(/\*/g, "%2A");
},
decode : function (encoded) {
if (!encoded) {
return '';
}
return decodeURIComponent(encoded);
}
};
function HmacSha1Signature(signatureBaseString, consumerSecret, tokenSecret) {
this._rfc3986 = new Rfc3986();
this._text = signatureBaseString;
this._key = this._rfc3986.encode(consumerSecret) + '&' + this._rfc3986.encode(tokenSecret);
this._base64EncodedHash = new HmacSha1(this._text, this._key).getBase64EncodedHash();
}
HmacSha1Signature.prototype = {
generate : function (encode) {
return encode === false ?
this._base64EncodedHash :
this._rfc3986.encode(this._base64EncodedHash);
}
};
function HmacSha1(text, key) {
// load CryptoJs in the browser or in node
this._cryptoJS = isNode ? require('crypto-js') : CryptoJS;
this._text = text || '';
this._key = key || '';
this._hash = this._cryptoJS.HmacSHA1(this._text, this._key);
}
HmacSha1.prototype = {
getBase64EncodedHash : function () {
return this._hash.toString(this._cryptoJS.enc.Base64);
}
};
var oauthSignature = new OAuthSignature();
oauthSignature.SignatureBaseString = SignatureBaseString;
oauthSignature.HttpMethodElement = HttpMethodElement;
oauthSignature.UrlElement = UrlElement;
oauthSignature.ParametersElement = ParametersElement;
oauthSignature.ParametersLoader = ParametersLoader;
oauthSignature.Rfc3986 = Rfc3986;
oauthSignature.HmacSha1Signature = HmacSha1Signature;
oauthSignature.HmacSha1 = HmacSha1;
// support for the browser and nodejs
if (isNode) {
module.exports = oauthSignature;
} else {
window.oauthSignature = oauthSignature;
}
})();