Skip to content

Commit

Permalink
Merge pull request #1413 from simov/fix-basic-auth
Browse files Browse the repository at this point in the history
Fix basic auth
  • Loading branch information
nylen committed Feb 11, 2015
2 parents 25ee80f + a60482b commit 2f5f5e1
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 255 deletions.
26 changes: 22 additions & 4 deletions lib/auth.js
Expand Up @@ -8,8 +8,9 @@ var md5 = helpers.md5
, toBase64 = helpers.toBase64


function Auth () {
function Auth (request) {
// define all public properties here
this.request = request
this.hasAuth = false
this.sentAuth = false
this.bearerToken = null
Expand Down Expand Up @@ -108,11 +109,28 @@ Auth.prototype.digest = function (method, path, authHeader) {
return authHeader
}

Auth.prototype.response = function (method, path, headers) {
Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
var self = this
, request = self.request

var authHeader
if (bearer !== undefined) {
authHeader = self.bearer(bearer, sendImmediately)
} else {
authHeader = self.basic(user, pass, sendImmediately)
}
if (authHeader) {
request.setHeader('authorization', authHeader)
}
}

Auth.prototype.onResponse = function (response) {
var self = this
, request = self.request

if (!self.hasAuth || self.sentAuth) { return null }

var c = caseless(headers)
var c = caseless(response.headers)

var authHeader = c.get('www-authenticate')
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
Expand All @@ -126,7 +144,7 @@ Auth.prototype.response = function (method, path, headers) {
return self.bearer(self.bearerToken, true)

case 'digest':
return self.digest(method, path, authHeader)
return self.digest(request.method, request.path, authHeader)
}
}

Expand Down
104 changes: 104 additions & 0 deletions lib/multipart.js
@@ -0,0 +1,104 @@
'use strict'

var uuid = require('node-uuid')
, CombinedStream = require('combined-stream')
, isstream = require('isstream')


function Multipart (request) {
this.request = request
this.boundary = uuid()
this.chunked = false
this.body = null
}

Multipart.prototype.isChunked = function (options) {
var self = this
, chunked = false
, parts = options.data || options

if (!parts.forEach) {
throw new Error('Argument error, options.multipart.')
}

if (self.request.getHeader('transfer-encoding') === 'chunked') {
chunked = true
}

if (options.chunked !== undefined) {
chunked = options.chunked
}

if (!chunked) {
parts.forEach(function (part) {
if(typeof part.body === 'undefined') {
throw new Error('Body attribute missing in multipart.')
}
if (isstream(part.body)) {
chunked = true
}
})
}

return chunked
}

Multipart.prototype.setHeaders = function (chunked) {
var self = this

if (chunked && !self.request.hasHeader('transfer-encoding')) {
self.request.setHeader('transfer-encoding', 'chunked')
}

var header = self.request.getHeader('content-type')
var contentType = (!header || header.indexOf('multipart') === -1)
? 'multipart/related'
: header.split(';')[0]

self.request.setHeader('content-type', contentType + '; boundary=' + self.boundary)
}

Multipart.prototype.build = function (parts, chunked) {
var self = this
var body = chunked ? new CombinedStream() : []

function add (part) {
return chunked ? body.append(part) : body.push(new Buffer(part))
}

if (self.request.preambleCRLF) {
add('\r\n')
}

parts.forEach(function (part) {
var preamble = '--' + self.boundary + '\r\n'
Object.keys(part).forEach(function (key) {
if (key === 'body') { return }
preamble += key + ': ' + part[key] + '\r\n'
})
preamble += '\r\n'
add(preamble)
add(part.body)
add('\r\n')
})
add('--' + self.boundary + '--')

if (self.request.postambleCRLF) {
add('\r\n')
}

return body
}

Multipart.prototype.onRequest = function (options) {
var self = this

var chunked = self.isChunked(options)
, parts = options.data || options

self.setHeaders(chunked)
self.chunked = chunked
self.body = self.build(parts, chunked)
}

exports.Multipart = Multipart
35 changes: 20 additions & 15 deletions lib/oauth.js
Expand Up @@ -7,7 +7,11 @@ var querystring = require('querystring')
, oauth = require('oauth-sign')


exports.buildParams = function (_oauth, uri, method, query, form, qsLib) {
function OAuth (request) {
this.request = request
}

OAuth.prototype.buildParams = function (_oauth, uri, method, query, form, qsLib) {
var oa = {}
for (var i in _oauth) {
oa['oauth_' + i] = _oauth[i]
Expand Down Expand Up @@ -54,7 +58,7 @@ exports.buildParams = function (_oauth, uri, method, query, form, qsLib) {
return oa
}

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

var params = Object.keys(oa).filter(function (i) {
Expand All @@ -71,13 +75,15 @@ exports.concatParams = function (oa, sep, 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
OAuth.prototype.onRequest = function (_oauth) {
var self = this
, request = self.request

var uri = request.uri || {}
, method = request.method || ''
, headers = caseless(request.headers)
, body = request.body || ''
, qsLib = request.qsLib || qs

var form
, query
Expand All @@ -99,23 +105,22 @@ exports.oauth = function (args) {

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

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

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

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

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

return {oauth:data, transport:transport}
}

exports.OAuth = OAuth
146 changes: 146 additions & 0 deletions lib/redirect.js
@@ -0,0 +1,146 @@
'use strict'

var url = require('url')
var isUrl = /^https?:/

function Redirect (request) {
this.request = request
this.followRedirect = true
this.followRedirects = true
this.followAllRedirects = false
this.allowRedirect = function () {return true}
this.maxRedirects = 10
this.redirects = []
this.redirectsFollowed = 0
}

Redirect.prototype.onRequest = function () {
var self = this
, request = self.request

if (request.maxRedirects !== undefined) {
self.maxRedirects = request.maxRedirects
}
if (typeof request.followRedirect === 'function') {
self.allowRedirect = request.followRedirect
}
if (request.followRedirect !== undefined) {
self.followRedirects = !!request.followRedirect
}
if (request.followAllRedirects !== undefined) {
self.followAllRedirects = request.followAllRedirects
}
if (self.followRedirects || self.followAllRedirects) {
self.redirects = self.redirects || []
}
}

Redirect.prototype.redirectTo = function (response) {
var self = this
, request = self.request

var redirectTo = null
if (response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) {
var location = response.caseless.get('location')
// debug('redirect', location)

if (self.followAllRedirects) {
redirectTo = location
} else if (self.followRedirects) {
switch (request.method) {
case 'PATCH':
case 'PUT':
case 'POST':
case 'DELETE':
// Do not follow redirects
break
default:
redirectTo = location
break
}
}
} else if (response.statusCode === 401) {
var authHeader = request._auth.onResponse(response)
if (authHeader) {
request.setHeader('authorization', authHeader)
redirectTo = request.uri
}
}
return redirectTo
}

Redirect.prototype.onResponse = function (response) {
var self = this
, request = self.request

var redirectTo = self.redirectTo(response)
if (!redirectTo || !self.allowRedirect.call(request, response)) {
return false
}


// debug('redirect to', redirectTo)

// ignore any potential response body. it cannot possibly be useful
// to us at this point.
if (request._paused) {
response.resume()
}

if (self.redirectsFollowed >= self.maxRedirects) {
request.emit('error', new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + request.uri.href))
return false
}
self.redirectsFollowed += 1

if (!isUrl.test(redirectTo)) {
redirectTo = url.resolve(request.uri.href, redirectTo)
}

var uriPrev = request.uri
request.uri = url.parse(redirectTo)

// handle the case where we change protocol from https to http or vice versa
if (request.uri.protocol !== uriPrev.protocol) {
request._updateProtocol()
}

self.redirects.push(
{ statusCode : response.statusCode
, redirectUri: redirectTo
}
)
if (self.followAllRedirects && response.statusCode !== 401 && response.statusCode !== 307) {
request.method = 'GET'
}
// request.method = 'GET' // Force all redirects to use GET || commented out fixes #215
delete request.src
delete request.req
delete request.agent
delete request._started
if (response.statusCode !== 401 && response.statusCode !== 307) {
// Remove parameters from the previous response, unless this is the second request
// for a server that requires digest authentication.
delete request.body
delete request._form
if (request.headers) {
request.removeHeader('host')
request.removeHeader('content-type')
request.removeHeader('content-length')
if (request.uri.hostname !== request.originalHost.split(':')[0]) {
// Remove authorization if changing hostnames (but not if just
// changing ports or protocols). This matches the behavior of curl:
// https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710
request.removeHeader('authorization')
}
}
}

request.emit('redirect')

request.init()

return true
}

exports.Redirect = Redirect

0 comments on commit 2f5f5e1

Please sign in to comment.