Comparing version 1.6.2 to 1.7.0
@@ -20,2 +20,3 @@ module.exports = Cloudant; | ||
var nanodebug = require('debug')('nano'); | ||
var async = require('async'); | ||
@@ -52,2 +53,9 @@ | ||
} | ||
if (theurl === null) { | ||
if (callback) { | ||
return callback('invalid url', null); | ||
} else { | ||
throw(new Error('invalid url')); | ||
} | ||
} | ||
@@ -247,47 +255,36 @@ // keep connections alive by default | ||
var nano = this; | ||
if (!callback) { | ||
callback = login; | ||
login = null; | ||
} | ||
// Only call back once. | ||
var inner_callback = callback; | ||
callback = function(er, result, cookie) { | ||
inner_callback(er, result, cookie); | ||
inner_callback = function() {}; | ||
}; | ||
var cookie = null; | ||
var done = {welcome:false, session:false, auth:true}; | ||
nano.session( function(er, body) { returned('session', er, body); }); | ||
nano.relax({db:""}, function(er, body) { returned('welcome', er, body); }); | ||
// If credentials are supplied, authenticate to get a cookie. | ||
if (login && login.username && login.password) { | ||
done.auth = false; | ||
nano.auth(login.username, login.password, function(er, body, headers) { | ||
returned('auth', er, body, headers); | ||
}); | ||
} | ||
function returned(type, er, body, headers) { | ||
if (er) | ||
return callback(er); | ||
debug('Pong/%s %j', type, body); | ||
done[type] = body; | ||
if (type == 'auth') { | ||
if (headers['set-cookie'] && headers['set-cookie'][0]) { | ||
cookie = headers['set-cookie'][0].replace(/;.*$/, ''); | ||
async.series([ | ||
function(done) { | ||
if (login && login.username && login.password) { | ||
done.auth = false; | ||
nano.auth(login.username, login.password, function(e, b, h) { | ||
cookie = (h && h['set-cookie']) || null; | ||
if (cookie) { | ||
cookie = cookie[0]; | ||
} | ||
done(null, b); | ||
}); | ||
} else { | ||
done(null, null); | ||
} | ||
}, | ||
function(done) { | ||
nano.session(function(e, b, h) { | ||
done(null, b); | ||
}); | ||
}, | ||
function(done) { | ||
nano.relax({db:''}, function(e, b, h) { | ||
done(null, b); | ||
}) | ||
} | ||
], function(err, data) { | ||
var body = (data && data[2]) || {}; | ||
body.userCtx = (data && data[1] && data[1].userCtx) || {}; | ||
callback(null, body, cookie); | ||
}); | ||
} | ||
if (done.welcome && done.session && done.auth) { | ||
// Return the CouchDB "Welcome" body but with the userCtx added in. | ||
done.welcome.userCtx = done.session.userCtx; | ||
callback(null, done.welcome, cookie); | ||
} | ||
} | ||
} | ||
@@ -9,2 +9,3 @@ // reconfigure deals with the various ways the credentials can be passed in | ||
// or { url: "https://mykey:mypassword@myaccount.cloudant.com"} | ||
// or { instanceName: "mycloudantservice", vcapServices: JSON.parse(process.env.VCAP_SERVICES)} | ||
@@ -19,5 +20,13 @@ var url = require('url'); | ||
if (config.url) { | ||
// parse the URL | ||
var parsed = null; | ||
try { | ||
var parsed = url.parse(config.url); | ||
} catch(e) { | ||
parsed = null; | ||
}; | ||
if (!config.url || !parsed || !parsed.hostname || !parsed.protocol || !parsed.slashes) { | ||
return null; | ||
} | ||
// parse the URL | ||
var parsed = url.parse(config.url); | ||
@@ -43,3 +52,26 @@ // enforce HTTPS for *cloudant.com domains | ||
outUrl = config.url; | ||
} else if (config.vcapServices) { | ||
cloudantServices = config.vcapServices.cloudantNoSQLDB; | ||
if (!cloudantServices || cloudantServices.length == 0) { | ||
throw new Error('Missing Cloudant service in vcapServices'); | ||
} | ||
for (var i = 0; i < cloudantServices.length; i++) { | ||
if (config.instanceName == undefined || cloudantServices[i].name == config.instanceName) { | ||
var credentials = cloudantServices[i].credentials; | ||
if (credentials && credentials.url) { | ||
outUrl = credentials.url; | ||
break; | ||
} else { | ||
throw new Error('Invalid Cloudant service in vcapServices'); | ||
} | ||
} | ||
} | ||
if (!outUrl) { | ||
throw new Error('Missing Cloudant service in vcapServices'); | ||
} | ||
} else { | ||
@@ -75,7 +107,7 @@ // An account can be just the username, or the full cloudant URL. | ||
// Issue: cloudant/nodejs-cloudant#129 | ||
if (outUrl.slice(-1) == '/') { | ||
if (outUrl && outUrl.slice(-1) == '/') { | ||
outUrl = outUrl.slice(0, -1); | ||
} | ||
return outUrl; | ||
return (outUrl || null); | ||
}; | ||
@@ -82,0 +114,0 @@ |
@@ -7,3 +7,3 @@ { | ||
"repository": "git://github.com/cloudant/nodejs-cloudant", | ||
"version": "1.6.2", | ||
"version": "1.7.0", | ||
"author": "Jason Smith <jasons@us.ibm.com>", | ||
@@ -10,0 +10,0 @@ "contributors": [ |
@@ -146,2 +146,11 @@ # Cloudant Node.js Client | ||
Running on Bluemix? You can initialize Cloudant directly from the `VCAP_SERVICES` environment variable: | ||
~~~ js | ||
var Cloudant = require('cloudant'); | ||
var cloudant = Cloudant({instanceName: 'foo', vcapServices: JSON.parse(process.env.VCAP_SERVICES)}); | ||
~~~ | ||
Note, if you only have a single Cloudant service then specifying the `instanceName` isn't required. | ||
You can optionally provide a callback to the Cloudant initialization function. This will make the library automatically "ping" Cloudant to confirm the connection and that your credentials work. | ||
@@ -213,3 +222,3 @@ | ||
This library can be used with one of three `request` plugins: | ||
This library can be used with one of these `request` plugins: | ||
@@ -222,3 +231,5 @@ 1. `default` - the default [request](https://www.npmjs.com/package/request) library plugin. This uses Node.js's callbacks to communicate Cloudant's replies | ||
The "retry" plugin will automatically retry your request with exponential back-off. The 'retry' plugin can be used to stream data. | ||
4. custom plugin - you may also supply your own function which will be called to make API calls. | ||
4. `cookieauth` - this plugin will automatically swap your Cloudant credentials for a cookie transparently for you. It will handle the authentication for you | ||
and ensure that the cookie is refreshed. The 'cookieauth' plugin can be used to stream data. | ||
5. custom plugin - you may also supply your own function which will be called to make API calls. | ||
@@ -263,2 +274,19 @@ #### The 'promises' Plugins | ||
#### The 'cookieauth' plugin | ||
When initialising the Cloudant library, you can opt to use the 'retry' plugin: | ||
```js | ||
var cloudant = Cloudant({url: myurl, plugin:'cookieauth'}); | ||
var mydb = cloudant.db.use('mydb'); | ||
mydb.get('mydoc', function(err, data) { | ||
}); | ||
``` | ||
The above code will transparently call `POST /_session` to exchange your credentials for a cookie and then call `GET /mydoc` to fetch the document. | ||
Subsequent calls to the same `cloudant` instance will simply use cookie authentication from that point. The library will automatically ensure that the cookie remains | ||
up-to-date by calling Cloudant on an hourly basis to refresh the cookie. | ||
#### Custom plugin | ||
@@ -265,0 +293,0 @@ |
@@ -126,1 +126,129 @@ /** | ||
}); | ||
describe('cookieauth plugin', function() { | ||
it('should return a stream', function(done) { | ||
var mocks = nock(SERVER) | ||
.post('/_session').reply(200, { ok: true}) | ||
.get('/' + MYDB).reply(200, { ok: true}); | ||
var cloudant = Cloudant({plugin:'cookieauth', account:ME, password: PASSWORD}); | ||
var db = cloudant.db.use(MYDB); | ||
var p = db.info(function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
// check that we use all the nocked API calls | ||
assert.equal(mocks.isDone(), true); | ||
done(); | ||
}); | ||
assert.equal(p instanceof require('stream').PassThrough, true); | ||
}); | ||
it('should authenticate before attempting API call', function(done) { | ||
var mocks = nock(SERVER) | ||
.post('/_session', {name: ME, password: PASSWORD}).reply(200, { ok: true, info: { }, userCtx: { name: ME, roles: ['_admin']}}) | ||
.get('/' + MYDB + '/mydoc').reply(200, { _id: 'mydoc', _rev: '1-123', ok: true}); | ||
var cloudant = Cloudant({plugin:'cookieauth', account:ME, password: PASSWORD}); | ||
var db = cloudant.db.use(MYDB); | ||
var p = db.get('mydoc', function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
data.should.have.property._id; | ||
data.should.have.property._rev; | ||
data.should.have.property.ok; | ||
// check that we use all the nocked API calls | ||
assert.equal(mocks.isDone(), true); | ||
done(); | ||
}); | ||
}); | ||
it('should fail with incorrect authentication', function(done) { | ||
var mocks = nock(SERVER) | ||
.post('/_session', {name: ME, password: PASSWORD}).reply(401, {error:'unauthorized', reason: 'Name or password is incorrect.'}); | ||
var cloudant = Cloudant({plugin:'cookieauth', account:ME, password: PASSWORD}); | ||
var db = cloudant.db.use(MYDB); | ||
var p = db.get('mydoc', function(err, data) { | ||
assert.equal(data, null); | ||
err.should.be.an.Object; | ||
err.should.have.property.error; | ||
err.should.have.property.reason; | ||
// check that we use all the nocked API calls | ||
assert.equal(mocks.isDone(), true); | ||
done(); | ||
}); | ||
}); | ||
it('should only authenticate once', function(done) { | ||
var mocks = nock(SERVER) | ||
.post('/_session', {name: ME, password: PASSWORD}).reply(200, { ok: true, info: { }, userCtx: { name: ME, roles: ['_admin']}}, { 'Set-Cookie': 'AuthSession=xyz; Version=1; Path=/; HttpOnly' }) | ||
.get('/' + MYDB + '/mydoc').reply(200, { _id: 'mydoc', _rev: '1-123', ok: true}) | ||
.get('/' + MYDB + '/mydoc').reply(200, { _id: 'mydoc', _rev: '1-123', ok: true}); | ||
var cloudant = Cloudant({plugin:'cookieauth', account:ME, password: PASSWORD}); | ||
var db = cloudant.db.use(MYDB); | ||
var p = db.get('mydoc', function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
data.should.have.property._id; | ||
data.should.have.property._rev; | ||
data.should.have.property.ok; | ||
db.get('mydoc', function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
data.should.have.property._id; | ||
data.should.have.property._rev; | ||
data.should.have.property.ok; | ||
// check that we use all the nocked API calls | ||
assert.equal(mocks.isDone(), true); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should not authenticate without credentials', function(done) { | ||
var mocks = nock(SERVER) | ||
.get('/' + MYDB + '/mydoc').reply(200, { _id: 'mydoc', _rev: '1-123', ok: true}); | ||
var cloudant = Cloudant({plugin:'cookieauth', url: SERVER}); | ||
var db = cloudant.db.use(MYDB); | ||
var p = db.get('mydoc', function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
data.should.have.property._id; | ||
data.should.have.property._rev; | ||
data.should.have.property.ok; | ||
// check that we use all the nocked API calls | ||
assert.equal(mocks.isDone(), true); | ||
done(); | ||
}); | ||
}); | ||
it('should work with asynchronous instantiation', function(done) { | ||
var mocks = nock(SERVER) | ||
.post('/_session', {name: ME, password: PASSWORD}).reply(200, { ok: true, info: { }, userCtx: { name: ME, roles: ['_admin']}}, { 'Set-Cookie': 'AuthSession=xyz; Version=1; Path=/; HttpOnly' }) | ||
.get('/_session').reply(200, { ok: true, info: { }, userCtx: { name: ME, roles: ['_admin']}}) | ||
.get('*').reply(200, {"couchdb":"Welcome","version":"2.0.0","vendor":{"name":"IBM Cloudant","version":"5662","variant":"paas"},"features":["geo"]}) | ||
var cloudant = Cloudant({plugin:'cookieauth', account:ME, password: PASSWORD}, function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
data.should.have.property.userCtx; | ||
done(); | ||
}); | ||
}); | ||
it('should work with asynchronous instantiation with no credentials', function(done) { | ||
var mocks = nock(SERVER) | ||
.get('/_session').reply(200, { ok: true, info: { }, userCtx: { name: ME, roles: ['_admin']}}) | ||
.get('*').reply(200, {"couchdb":"Welcome","version":"2.0.0","vendor":{"name":"IBM Cloudant","version":"5662","variant":"paas"},"features":["geo"]}) | ||
var cloudant = Cloudant({plugin:'cookieauth', url: SERVER}, function(err, data) { | ||
assert.equal(err, null); | ||
data.should.be.an.Object; | ||
data.should.have.property.userCtx; | ||
done(); | ||
}); | ||
}); | ||
}); |
var should = require('should'); | ||
var reconfigure = require('../lib/reconfigure.js'); | ||
var assert = require('assert'); | ||
@@ -106,2 +107,103 @@ describe('Reconfigure', function() { | ||
it('gets first URL from vcap containing single service', function(done) { | ||
var config = {vcapServices: {cloudantNoSQLDB: [ | ||
{credentials: {url: "http://mykey:mypassword@mydomain.cloudant.com"}} | ||
]}}; | ||
var url = reconfigure(config); | ||
url.should.be.a.String; | ||
url.should.equal("http://mykey:mypassword@mydomain.cloudant.com"); | ||
done(); | ||
}); | ||
it('gets URL by instance name from vcap containing single service', function(done) { | ||
var config = {instanceName: "serviceA", vcapServices: {cloudantNoSQLDB: [ | ||
{name: "serviceA", credentials: {url: "http://mykey:mypassword@mydomain.cloudant.com"}} | ||
]}}; | ||
var url = reconfigure(config); | ||
url.should.be.a.String; | ||
url.should.equal("http://mykey:mypassword@mydomain.cloudant.com"); | ||
done(); | ||
}); | ||
it('gets first URL from vcap containing multiple services', function(done) { | ||
var config = {vcapServices: {cloudantNoSQLDB: [ | ||
{credentials: {url: "http://mykey:mypassword@mydomain.cloudant.com"}}, | ||
{credentials: {url: "http://foo.bar"}}, | ||
{credentials: {url: "http://foo.bar"}} | ||
]}}; | ||
var url = reconfigure(config); | ||
url.should.be.a.String; | ||
url.should.equal("http://mykey:mypassword@mydomain.cloudant.com"); | ||
done(); | ||
}); | ||
it('gets URL by instance name from vcap containing multiple services', function(done) { | ||
var config = {instanceName: 'serviceC', vcapServices: {cloudantNoSQLDB: [ | ||
{name: "serviceA", credentials: {url: "http://foo.bar"}}, | ||
{name: "serviceB", credentials: {url: "http://foo.bar"}}, | ||
{name: "serviceC", credentials: {url: "http://mykey:mypassword@mydomain.cloudant.com"}} | ||
]}}; | ||
var url = reconfigure(config); | ||
url.should.be.a.String; | ||
url.should.equal("http://mykey:mypassword@mydomain.cloudant.com"); | ||
done(); | ||
}); | ||
it('errors for empty vcap', function(done) { | ||
var config = {vcapServices: {}} | ||
should(function () { reconfigure(config); }).throw("Missing Cloudant service in vcapServices"); | ||
done(); | ||
}); | ||
it('errors for no services in vcap', function(done) { | ||
var config = {vcapServices: {cloudantNoSQLDB: []}} | ||
should(function () { reconfigure(config); }).throw("Missing Cloudant service in vcapServices"); | ||
done(); | ||
}); | ||
it('errors for missing service in vcap', function(done) { | ||
var config = {instanceName: 'serviceC', vcapServices: {cloudantNoSQLDB: [ | ||
{name: "serviceA", credentials: {url: "http://foo.bar"}}, | ||
{name: "serviceB", credentials: {url: "http://foo.bar"}} // missing "serviceC" | ||
]}}; | ||
should(function () { reconfigure(config); }).throw("Missing Cloudant service in vcapServices"); | ||
done(); | ||
}); | ||
it('errors for invalid service in vcap - missing credentials', function(done) { | ||
var config = {vcapServices: {cloudantNoSQLDB: [ | ||
{name: "serviceA"} // invalid service, missing credentials | ||
]}}; | ||
should(function () { reconfigure(config); }).throw('Invalid Cloudant service in vcapServices'); | ||
done(); | ||
}); | ||
it('errors for invalid service in vcap - missing url', function(done) { | ||
var config = {vcapServices: {cloudantNoSQLDB: [ | ||
{name: "serviceA", credentials: {}} // invalid service, missing url | ||
]}}; | ||
should(function () { reconfigure(config); }).throw("Invalid Cloudant service in vcapServices"); | ||
done(); | ||
}); | ||
it('detects bad urls', function(done) { | ||
var credentials = { url: 'invalid' }; | ||
var url = reconfigure(credentials); | ||
assert.equal(url, null); | ||
var credentials = { url: '' }; | ||
var url = reconfigure(credentials); | ||
assert.equal(url, null); | ||
var credentials = { url: 'http://' }; | ||
var url = reconfigure(credentials); | ||
assert.equal(url, null); | ||
var credentials = { }; | ||
var url = reconfigure(credentials); | ||
assert.equal(url, null); | ||
var credentials = 'invalid'; | ||
var url = reconfigure(credentials); | ||
assert.equal(url, null); | ||
done(); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
153116
2621
1033