Skip to content

Commit

Permalink
Merge pull request #1366 from simov/refactor-oauth
Browse files Browse the repository at this point in the history
Refactor OAuth into separate module
  • Loading branch information
nylen committed Jan 27, 2015
2 parents 0e22b30 + bf43ade commit e5c295b
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 88 deletions.
121 changes: 121 additions & 0 deletions lib/oauth.js
@@ -0,0 +1,121 @@
'use strict'

var querystring = require('querystring')
, qs = require('qs')
, caseless = require('caseless')
, uuid = require('node-uuid')
, oauth = require('oauth-sign')


exports.buildParams = function (_oauth, uri, method, query, form, qsLib) {
var oa = {}
for (var i in _oauth) {
oa['oauth_' + i] = _oauth[i]
}
if (!oa.oauth_version) {
oa.oauth_version = '1.0'
}
if (!oa.oauth_timestamp) {
oa.oauth_timestamp = Math.floor( Date.now() / 1000 ).toString()
}
if (!oa.oauth_nonce) {
oa.oauth_nonce = uuid().replace(/-/g, '')
}
if (!oa.oauth_signature_method) {
oa.oauth_signature_method = 'HMAC-SHA1'
}

var consumer_secret_or_private_key = oa.oauth_consumer_secret || oa.oauth_private_key
delete oa.oauth_consumer_secret
delete oa.oauth_private_key

var token_secret = oa.oauth_token_secret
delete oa.oauth_token_secret

var realm = oa.oauth_realm
delete oa.oauth_realm
delete oa.oauth_transport_method

var baseurl = uri.protocol + '//' + uri.host + uri.pathname
var params = qsLib.parse([].concat(query, form, qsLib.stringify(oa)).join('&'))

oa.oauth_signature = oauth.sign(
oa.oauth_signature_method,
method,
baseurl,
params,
consumer_secret_or_private_key,
token_secret)

if (realm) {
oa.realm = realm
}

return oa
}

exports.concatParams = function (oa, sep, wrap) {
wrap = wrap || ''

var params = Object.keys(oa).filter(function (i) {
return i !== 'realm' && i !== 'oauth_signature'
}).sort()

if (oa.realm) {
params.splice(0, 1, 'realm')
}
params.push('oauth_signature')

return params.map(function (i) {
return i + '=' + wrap + oauth.rfc3986(oa[i]) + wrap
}).join(sep)
}

exports.oauth = function (args) {
var uri = args.uri || {}
, method = args.method || ''
, headers = caseless(args.headers)
, body = args.body || ''
, _oauth = args.oauth || {}
, qsLib = args.qsLib || qs

var form
, query
, contentType = headers.get('content-type') || ''
, formContentType = 'application/x-www-form-urlencoded'
, transport = _oauth.transport_method || 'header'

if (contentType.slice(0, formContentType.length) === formContentType) {
contentType = formContentType
form = body
}
if (uri.query) {
query = uri.query
}
if (transport === 'body' && (method !== 'POST' || contentType !== formContentType)) {
throw new Error('oauth: transport_method of \'body\' requires \'POST\' ' +
'and content-type \'' + formContentType + '\'')
}

var oa = this.buildParams(_oauth, uri, method, query, form, qsLib)

var data
switch (transport) {
case 'header':
data = 'OAuth ' + this.concatParams(oa, ',', '"')
break

case 'query':
data = (query ? '&' : '?') + this.concatParams(oa, '&')
break

case 'body':
data = (form ? form + '&' : '') + this.concatParams(oa, '&')
break

default:
throw new Error('oauth: transport_method invalid')
}

return {oauth:data, transport:transport}
}
90 changes: 16 additions & 74 deletions request.js
Expand Up @@ -10,7 +10,6 @@ var http = require('http')
, zlib = require('zlib')
, helpers = require('./lib/helpers')
, bl = require('bl')
, oauth = require('oauth-sign')
, hawk = require('hawk')
, aws = require('aws-sign2')
, httpSignature = require('http-signature')
Expand All @@ -28,6 +27,7 @@ var http = require('http')
, isstream = require('isstream')
, getProxyFromURI = require('./lib/getProxyFromURI')
, Auth = require('./lib/auth').Auth
, oauth = require('./lib/oauth')

var safeStringify = helpers.safeStringify
, md5 = helpers.md5
Expand Down Expand Up @@ -1516,87 +1516,29 @@ Request.prototype.hawk = function (opts) {

Request.prototype.oauth = function (_oauth) {
var self = this
var form, query, contentType = '', formContentType = 'application/x-www-form-urlencoded'

if (self.hasHeader('content-type') &&
self.getHeader('content-type').slice(0, formContentType.length) === formContentType) {
contentType = formContentType
form = self.body
}
if (self.uri.query) {
query = self.uri.query
}

var transport = _oauth.transport_method || 'header'
if (transport === 'body' && (
self.method !== 'POST' || contentType !== formContentType)) {

throw new Error('oauth.transport_method of \'body\' requires \'POST\' ' +
'and content-type \'' + formContentType + '\'')
}

delete _oauth.transport_method

var oa = {}
for (var i in _oauth) {
oa['oauth_' + i] = _oauth[i]
}
if ('oauth_realm' in oa) {
delete oa.oauth_realm
}
if (!oa.oauth_version) {
oa.oauth_version = '1.0'
}
if (!oa.oauth_timestamp) {
oa.oauth_timestamp = Math.floor( Date.now() / 1000 ).toString()
}
if (!oa.oauth_nonce) {
oa.oauth_nonce = uuid().replace(/-/g, '')
}
if (!oa.oauth_signature_method) {
oa.oauth_signature_method = 'HMAC-SHA1'
}

var consumer_secret_or_private_key = oa.oauth_consumer_secret || oa.oauth_private_key
delete oa.oauth_consumer_secret
delete oa.oauth_private_key
var token_secret = oa.oauth_token_secret
delete oa.oauth_token_secret

var baseurl = self.uri.protocol + '//' + self.uri.host + self.uri.pathname
var params = self.qsLib.parse([].concat(query, form, self.qsLib.stringify(oa)).join('&'))

var signature = oauth.sign(
oa.oauth_signature_method,
self.method,
baseurl,
params,
consumer_secret_or_private_key,
token_secret)

var buildSortedParams = function (sep, wrap) {
wrap = wrap || ''
return Object.keys(oa).sort().map(function (i) {
return i + '=' + wrap + oauth.rfc3986(oa[i]) + wrap
}).join(sep) + sep + 'oauth_signature=' + wrap + oauth.rfc3986(signature) + wrap
}
var result = oauth.oauth({
uri: self.uri,
method: self.method,
headers: self.headers,
body: self.body,
oauth: _oauth,
qsLib: self.qsLib
})

if (transport === 'header') {
var realm = _oauth.realm ? 'realm="' + _oauth.realm + '",' : ''
self.setHeader('Authorization', 'OAuth ' + realm + buildSortedParams(',', '"'))
}
else if (transport === 'query') {
self.path += (query ? '&' : '?') + buildSortedParams('&')
if (result.transport === 'header') {
self.setHeader('Authorization', result.oauth)
}
else if (transport === 'body') {
self.body = (form ? form + '&' : '') + buildSortedParams('&')
else if (result.transport === 'query') {
self.path += result.oauth
}
else {
throw new Error('oauth.transport_method invalid')
else if (result.transport === 'body') {
self.body = result.oauth
}

return self
}

Request.prototype.jar = function (jar) {
var self = this
var cookies
Expand Down
28 changes: 14 additions & 14 deletions tests/test-oauth.js
Expand Up @@ -451,20 +451,20 @@ tape('query transport_method with qs parameter and existing query string in url'
t.notOk(r.headers.Authorization, 'oauth Authorization header should not be present with transport_method \'query\'')
t.notOk(r.path.match(/\?&/), 'there should be no ampersand at the beginning of the query')
t.equal('OB33pYjWAnf+xtOHN4Gmbdil168=', qs.parse(r.path).oauth_signature)
var matches = r.path.match(/\?(.*?)&(oauth.*$)/)
t.ok(matches, 'regex to split oauth parameters from qs parameters matched successfully')
var qsParams = qs.parse(matches[1])
var oauthParams = qs.parse(matches[2])

var i, paramNames = ['a2', 'a3[0]', 'a3[1]', 'c@', 'b5', 'c2']
for (i = 0; i < paramNames.length; i++) {
t.ok(qsParams.hasOwnProperty(paramNames[i]), 'Non-oauth query params should be first in query string: ' + paramNames[i])
}

paramNames = ['consumer_key', 'nonce', 'timestamp', 'version', 'signature', 'token', 'signature_method']
for (i = 0; i < paramNames.length; i++) {
var paramName = 'oauth_' + paramNames[i]
t.ok(oauthParams[paramName], 'OAuth query params should be included after request specific parameters: ' + paramName)

var params = qs.parse(r.path.split('?')[1])
, keys = Object.keys(params)

var paramNames = [
'a2', 'b5', 'a3[0]', 'a3[1]', 'c@', 'c2',
'realm', 'oauth_nonce', 'oauth_signature_method', 'oauth_timestamp',
'oauth_token', 'oauth_version', 'oauth_signature'
]

for (var i = 0; i < keys.length; i++) {
t.ok(keys[i] === paramNames[i],
'Non-oauth query params should be first, ' +
'OAuth query params should be second in query string')
}

r.abort()
Expand Down

0 comments on commit e5c295b

Please sign in to comment.