From bd4bf800d2b982b0c48a3fda087ad151697c629a Mon Sep 17 00:00:00 2001 From: Mark van Cuijk Date: Fri, 11 Apr 2014 10:08:12 +0200 Subject: [PATCH 1/2] Add support for RFC 6750 Bearer Tokens --- request.js | 22 +++++- tests/test-bearer-auth.js | 161 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 tests/test-bearer-auth.js diff --git a/request.js b/request.js index 39c5c0cd1..83b511bd3 100644 --- a/request.js +++ b/request.js @@ -284,7 +284,8 @@ Request.prototype.init = function (options) { self.auth( options.auth.user, options.auth.pass, - options.auth.sendImmediately + options.auth.sendImmediately, + options.auth.bearer ) } @@ -788,6 +789,11 @@ Request.prototype.onResponse = function (response) { redirectTo = self.uri break + case 'bearer': + self.auth(null, null, true, self._bearer) + redirectTo = self.uri + break + case 'digest': // TODO: More complete implementation of RFC 2617. // - check challenge.algorithm @@ -1150,7 +1156,19 @@ Request.prototype.getHeader = function (name, headers) { } var getHeader = Request.prototype.getHeader -Request.prototype.auth = function (user, pass, sendImmediately) { +Request.prototype.auth = function (user, pass, sendImmediately, bearer) { + if (bearer !== undefined) { + this._bearer = bearer + this._hasAuth = true + if (sendImmediately || typeof sendImmediately == 'undefined') { + if (typeof bearer === 'function') { + bearer = bearer() + } + this.setHeader('authorization', 'Bearer ' + bearer) + this._sentAuth = true + } + return this + } if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) { throw new Error('auth() received invalid user or password') } diff --git a/tests/test-bearer-auth.js b/tests/test-bearer-auth.js new file mode 100644 index 000000000..3f306c671 --- /dev/null +++ b/tests/test-bearer-auth.js @@ -0,0 +1,161 @@ +var assert = require('assert') + , http = require('http') + , request = require('../index') + ; + +var numBasicRequests = 0; + +var basicServer = http.createServer(function (req, res) { + console.error('Bearer auth server: ', req.method, req.url); + numBasicRequests++; + + var ok; + + if (req.headers.authorization) { + if (req.headers.authorization == 'Bearer theToken') { + ok = true; + } else { + // Bad auth header, don't send back WWW-Authenticate header + ok = false; + } + } else { + // No auth header, send back WWW-Authenticate header + ok = false; + res.setHeader('www-authenticate', 'Bearer realm="Private"'); + } + + if (req.url == '/post/') { + var expectedContent = 'data_key=data_value'; + req.on('data', function(data) { + assert.equal(data, expectedContent); + console.log('received request data: ' + data); + }); + assert.equal(req.method, 'POST'); + assert.equal(req.headers['content-length'], '' + expectedContent.length); + assert.equal(req.headers['content-type'], 'application/x-www-form-urlencoded; charset=utf-8'); + } + + if (ok) { + console.log('request ok'); + res.end('ok'); + } else { + console.log('status=401'); + res.statusCode = 401; + res.end('401'); + } +}); + +basicServer.listen(6767); + +var tests = [ + function(next) { + request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/test/', + 'auth': { + 'bearer': 'theToken', + 'sendImmediately': false + } + }, function(error, res, body) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 2); + next(); + }); + }, + + function(next) { + // If we don't set sendImmediately = false, request will send bearer auth + request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/test2/', + 'auth': { + 'bearer': 'theToken' + } + }, function(error, res, body) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 3); + next(); + }); + }, + + function(next) { + request({ + 'method': 'POST', + 'form': { 'data_key': 'data_value' }, + 'uri': 'http://localhost:6767/post/', + 'auth': { + 'bearer': 'theToken', + 'sendImmediately': false + } + }, function(error, res, body) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 5); + next(); + }); + }, + + function (next) { + request + .get('http://localhost:6767/test/') + .auth(null,null,false,"theToken") + .on('response', function (res) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 7); + next(); + }) + }, + + function (next) { + request + .get('http://localhost:6767/test/') + .auth(null,null,true,"theToken") + .on('response', function (res) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 8); + next(); + }) + }, + + function(next) { + request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/test/', + 'auth': { + 'bearer': function() { return 'theToken' }, + 'sendImmediately': false + } + }, function(error, res, body) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 10); + next(); + }); + }, + + function(next) { + // If we don't set sendImmediately = false, request will send bearer auth + request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/test2/', + 'auth': { + 'bearer': function() { return 'theToken' } + } + }, function(error, res, body) { + assert.equal(res.statusCode, 200); + assert.equal(numBasicRequests, 11); + next(); + }); + }, +]; + +function runTest(i) { + if (i < tests.length) { + tests[i](function() { + runTest(i + 1); + }); + } else { + console.log('All tests passed'); + basicServer.close(); + } +} + +runTest(0); From 7fefc99d39d3f3ebac1417bfbf978496846e816b Mon Sep 17 00:00:00 2001 From: Mark van Cuijk Date: Fri, 11 Apr 2014 10:27:17 +0200 Subject: [PATCH 2/2] Add documentation about auth.bearer --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 83a8aad0d..45f7fef63 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,14 @@ request.get('http://some.server.com/', { 'sendImmediately': false } }); +// or +request.get('http://some.server.com/').auth(null, null, true, 'bearerToken'); +// or +request.get('http://some.server.com/', { + 'auth': { + 'bearer': 'bearerToken' + } +}); ``` If passed as an option, `auth` should be a hash containing values `user` || `username`, `pass` || `password`, and `sendImmediately` (optional). The method form takes parameters `auth(username, password, sendImmediately)`. @@ -144,6 +152,8 @@ If passed as an option, `auth` should be a hash containing values `user` || `use Digest authentication is supported, but it only works with `sendImmediately` set to `false`; otherwise `request` will send basic authentication on the initial request, which will probably cause the request to fail. +Bearer authentication is supported, and is activated when the `bearer` value is available. The value may be either a `String` or a `Function` returning a `String`. Using a function to supply the bearer token is particularly useful if used in conjuction with `defaults` to allow a single function to supply the last known token at the time or sending a request or to compute one on the fly. + ## OAuth Signing ```javascript