@freeyeon2/oauth-1.0a
Version:
OAuth 1.0a Request Authorization for Node and Browser.
439 lines (379 loc) • 10.2 kB
JavaScript
if (typeof module !== 'undefined' && typeof exports !== 'undefined') {
module.exports = OAuth
}
/**
* Constructor
* @param {Object} opts consumer key and secret
*/
function OAuth(opts) {
if (!(this instanceof OAuth)) {
return new OAuth(opts)
}
if (!opts) {
opts = {}
}
if (!opts.consumer) {
throw new Error('consumer option is required')
}
this.consumer = opts.consumer
this.nonce_length = opts.nonce_length || 32
this.version = opts.version || '1.0'
this.parameter_seperator = opts.parameter_seperator || ', '
this.realm = opts.realm
this.timestamp = opts.timestamp || new Date().getTime()
this.nonce = opts.nonce || this.getNonce()
this.token = opts.token || null
this.secret = opts.secret || null
this.verifier = opts.verifier || null
if (typeof opts.last_ampersand === 'undefined') {
this.last_ampersand = true
} else {
this.last_ampersand = opts.last_ampersand
}
// default signature_method is 'PLAINTEXT'
this.signature_method = opts.signature_method || 'PLAINTEXT'
if (this.signature_method == 'PLAINTEXT' && !opts.hash_function) {
opts.hash_function = function (base_string, key) {
return key
}
}
if (!opts.hash_function) {
throw new Error('hash_function option is required')
}
this.hash_function = opts.hash_function
this.body_hash_function = opts.body_hash_function || this.hash_function
}
/**
* OAuth request authorize
* @param {Object} request data
* {
* method,
* url,
* data
* }
* @param {Object} key and secret token
* @return {Object} OAuth Authorized data
*/
OAuth.prototype.authorize = function (request, token) {
if (this.verifier) {
var oauth_data = {
oauth_consumer_key: this.consumer.key,
oauth_nonce: this.nonce,
oauth_signature_method: this.signature_method,
oauth_timestamp: this.timestamp,
oauth_version: this.version,
oauth_verifier: this.verifier,
oauth_token: this.token,
oauth_secret: this.secret,
}
} else {
var oauth_data = {
oauth_consumer_key: this.consumer.key,
oauth_nonce: this.nonce,
oauth_signature_method: this.signature_method,
oauth_timestamp: this.timestamp,
oauth_version: this.version,
}
}
if (!token) {
token = {}
}
if (token.key !== undefined) {
oauth_data.oauth_token = token.key
}
if (!request.data) {
request.data = {}
}
if (request.includeBodyHash) {
oauth_data.oauth_body_hash = this.getBodyHash(request, token.secret)
}
oauth_data.oauth_signature = this.getSignature(
request,
token.secret,
oauth_data,
)
return oauth_data
}
/**
* Create a OAuth Signature
* @param {Object} request data
* @param {Object} token_secret key and secret token
* @param {Object} oauth_data OAuth data
* @return {String} Signature
*/
OAuth.prototype.getSignature = function (request, token_secret, oauth_data) {
return this.hash_function(
this.getBaseString(request, oauth_data),
this.getSigningKey(token_secret),
)
}
/**
* Create a OAuth Body Hash
* @param {Object} request data
*/
OAuth.prototype.getBodyHash = function (request, token_secret) {
var body =
typeof request.data === 'string'
? request.data
: JSON.stringify(request.data)
if (!this.body_hash_function) {
throw new Error('body_hash_function option is required')
}
return this.body_hash_function(body, this.getSigningKey(token_secret))
}
/**
* Base String = Method + Base Url + ParameterString
* @param {Object} request data
* @param {Object} OAuth data
* @return {String} Base String
*/
OAuth.prototype.getBaseString = function (request, oauth_data) {
return (
request.method.toUpperCase() +
'&' +
this.percentEncode(this.getBaseUrl(request.url)) +
'&' +
this.percentEncode(this.getParameterString(request, oauth_data))
)
}
/**
* Get data from url
* -> merge with oauth data
* -> percent encode key & value
* -> sort
*
* @param {Object} request data
* @param {Object} OAuth data
* @return {Object} Parameter string data
*/
OAuth.prototype.getParameterString = function (request, oauth_data) {
var base_string_data
if (oauth_data.oauth_body_hash) {
base_string_data = this.sortObject(
this.percentEncodeData(
this.mergeObject(oauth_data, this.deParamUrl(request.url)),
),
)
} else {
base_string_data = this.sortObject(
this.percentEncodeData(
this.mergeObject(
oauth_data,
this.mergeObject(request.data, this.deParamUrl(request.url)),
),
),
)
}
var data_str = ''
//base_string_data to string
for (var i = 0; i < base_string_data.length; i++) {
var key = base_string_data[i].key
var value = base_string_data[i].value
// check if the value is an array
// this means that this key has multiple values
if (value && Array.isArray(value)) {
// sort the array first
value.sort()
var valString = ''
// serialize all values for this key: e.g. formkey=formvalue1&formkey=formvalue2
value.forEach(
function (item, i) {
valString += key + '=' + item
if (i < value.length) {
valString += '&'
}
}.bind(this),
)
data_str += valString
} else {
data_str += key + '=' + value + '&'
}
}
//remove the last character
data_str = data_str.substr(0, data_str.length - 1)
return data_str
}
/**
* Create a Signing Key
* @param {String} token_secret Secret Token
* @return {String} Signing Key
*/
OAuth.prototype.getSigningKey = function (token_secret) {
token_secret = token_secret || ''
if (!this.last_ampersand && !token_secret) {
return this.percentEncode(this.consumer.secret)
}
return (
this.percentEncode(this.consumer.secret) +
'&' +
this.percentEncode(token_secret)
)
}
/**
* Get base url
* @param {String} url
* @return {String}
*/
OAuth.prototype.getBaseUrl = function (url) {
return url.split('?')[0]
}
/**
* Get data from String
* @param {String} string
* @return {Object}
*/
OAuth.prototype.deParam = function (string) {
var arr = string.split('&')
var data = {}
for (var i = 0; i < arr.length; i++) {
var item = arr[i].split('=')
// '' value
item[1] = item[1] || ''
// check if the key already exists
// this can occur if the QS part of the url contains duplicate keys like this: ?formkey=formvalue1&formkey=formvalue2
if (data[item[0]]) {
// the key exists already
if (!Array.isArray(data[item[0]])) {
// replace the value with an array containing the already present value
data[item[0]] = [data[item[0]]]
}
// and add the new found value to it
data[item[0]].push(decodeURIComponent(item[1]))
} else {
// it doesn't exist, just put the found value in the data object
data[item[0]] = decodeURIComponent(item[1])
}
}
return data
}
/**
* Get data from url
* @param {String} url
* @return {Object}
*/
OAuth.prototype.deParamUrl = function (url) {
var tmp = url.split('?')
if (tmp.length === 1) return {}
return this.deParam(tmp[1])
}
/**
* Percent Encode
* @param {String} str
* @return {String} percent encoded string
*/
OAuth.prototype.percentEncode = function (str) {
return encodeURIComponent(str)
.replace(/\!/g, '%21')
.replace(/\*/g, '%2A')
.replace(/\'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
}
/**
* Percent Encode Object
* @param {Object} data
* @return {Object} percent encoded data
*/
OAuth.prototype.percentEncodeData = function (data) {
var result = {}
for (var key in data) {
var value = data[key]
// check if the value is an array
if (value && Array.isArray(value)) {
var newValue = []
// percentEncode every value
value.forEach(
function (val) {
newValue.push(this.percentEncode(val))
}.bind(this),
)
value = newValue
} else {
value = this.percentEncode(value)
}
result[this.percentEncode(key)] = value
}
return result
}
/**
* Get OAuth data as Header
* @param {Object} oauth_data
* @return {String} Header data key - value
*/
OAuth.prototype.toHeader = function (oauth_data) {
var sorted = this.sortObject(oauth_data)
var header_value = 'OAuth '
if (this.realm) {
header_value += 'realm="' + this.realm + '"' + this.parameter_seperator
}
for (var i = 0; i < sorted.length; i++) {
if (sorted[i].key.indexOf('oauth_') !== 0) continue
header_value +=
this.percentEncode(sorted[i].key) +
'="' +
this.percentEncode(sorted[i].value) +
'"' +
this.parameter_seperator
}
return {
Authorization: header_value.substr(
0,
header_value.length - this.parameter_seperator.length,
), //cut the last chars
}
}
/**
* Create a random word characters string with input length
* @return {String} a random word characters string
*/
OAuth.prototype.getNonce = function () {
var word_characters =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var result = ''
for (var i = 0; i < this.nonce_length; i++) {
result +=
word_characters[parseInt(Math.random() * word_characters.length, 10)]
}
return result
}
/**
* Get Current Unix TimeStamp
* @return {Int} current unix timestamp
*/
OAuth.prototype.getTimeStamp = function () {
return parseInt(new Date().getTime())
}
////////////////////// HELPER FUNCTIONS //////////////////////
/**
* Merge object
* @param {Object} obj1
* @param {Object} obj2
* @return {Object}
*/
OAuth.prototype.mergeObject = function (obj1, obj2) {
obj1 = obj1 || {}
obj2 = obj2 || {}
var merged_obj = obj1
for (var key in obj2) {
merged_obj[key] = obj2[key]
}
return merged_obj
}
/**
* Sort object by key
* @param {Object} data
* @return {Array} sorted array
*/
OAuth.prototype.sortObject = function (data) {
var keys = Object.keys(data)
var result = []
keys.sort()
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
result.push({
key: key,
value: data[key],
})
}
return result
}