Commit
* Re-use existing or upcoming mongoose connection * Re-use existing or upcoming node-mongodb-native connection * Accept full-featured MongoDB connection strings + advanced options * Compatible with legacy config * Replace callback by `connected` event * Add debug Fix #51, #58, #62, #66, #70, #85, #94, #96, #115, #117, #120 Fix #124, #128, #129, #130, #131, #133, #134
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,26 +7,27 @@ | |
/** | ||
* Module dependencies | ||
*/ | ||
|
||
var crypto = require('crypto'); | ||
var mongo = require('mongodb'); | ||
var url = require('url'); | ||
var util = require('util'); | ||
var debug = require('debug')('connect-mongo'); | ||
|
||
var MongoClient = mongo.MongoClient; | ||
var Db = mongo.Db; | ||
|
||
|
||
/** | ||
* Default options | ||
*/ | ||
|
||
var defaultOptions = {host: '127.0.0.1', | ||
port: 27017, | ||
collection: 'sessions', | ||
auto_reconnect: true, | ||
ssl: false, | ||
w: 1, | ||
defaultExpirationTime: 1000 * 60 * 60 * 24 * 14 | ||
}; | ||
var defaultOptions = { | ||
host: '127.0.0.1', | ||
port: 27017, | ||
collection: 'sessions', | ||
auto_reconnect: true, | ||
ssl: false, | ||
w: 1, | ||
defaultExpirationTime: 1000 * 60 * 60 * 24 * 14 // 14 days | ||
}; | ||
|
||
function defaultSerializer (session) { | ||
// Copy each property of the session to a new object | ||
|
@@ -53,157 +54,149 @@ module.exports = function(connect) { | |
|
||
/** | ||
* Initialize MongoStore with the given `options`. | ||
* Calls `readyCallback` when db connection is ready (mainly for testing purposes). | ||
* | ||
* @param {Object} options | ||
* @param {Function} readyCallback | ||
* @api public | ||
*/ | ||
|
||
function MongoStore(options, readyCallback) { | ||
function MongoStore(options) { | ||
options = options || {}; | ||
if(options.hash){ | ||
var collectionName = options.collection || defaultOptions.collection; | ||
|
||
Store.call(this, options); | ||
|
||
// Hash sid | ||
if (options.hash) { | ||
var defaultSalt = 'connect-mongo'; | ||
var defaultAlgorithm = 'sha1'; | ||
this.hash = {}; | ||
this.hash.salt = options.hash.salt ? options.hash.salt : defaultSalt; | ||
this.hash.algorithm = options.hash.algorithm ? options.hash.algorithm : defaultAlgorithm; | ||
} | ||
Store.call(this, options); | ||
|
||
if(options.url) { | ||
var db_url = url.parse(options.url); | ||
|
||
if (db_url.port) { | ||
options.port = parseInt(db_url.port); | ||
} | ||
|
||
if (db_url.pathname) { | ||
var pathname = db_url.pathname.split('/'); | ||
|
||
if (pathname.length >= 2 && pathname[1]) { | ||
options.db = pathname[1]; | ||
} | ||
|
||
if (pathname.length >= 3 && pathname[2]) { | ||
options.collection = pathname[2]; | ||
} | ||
} | ||
// Serialization | ||
if (options.stringify || (!('stringify' in options) && !('serialize' in options) && !('unserialize' in options))) { | ||
this._serialize_session = JSON.stringify; | ||
this._unserialize_session = JSON.parse; | ||
} else { | ||
this._serialize_session = options.serialize || defaultSerializer; | ||
this._unserialize_session = options.unserialize || identity; | ||
} | ||
|
||
if (db_url.hostname) { | ||
options.host = db_url.hostname; | ||
} | ||
// Expiration time | ||
this.defaultExpirationTime = options.defaultExpirationTime || defaultOptions.defaultExpirationTime; | ||
|
||
if (db_url.auth) { | ||
var auth = db_url.auth.split(':'); | ||
var self = this; | ||
|
||
if (auth.length >= 1) { | ||
options.username = auth[0]; | ||
} | ||
function changeState(newState) { | ||
debug('switched to state: %s', newState); | ||
self.state = newState; | ||
self.emit(newState); | ||
} | ||
|
||
if (auth.length >= 2) { | ||
options.password = auth[1]; | ||
} | ||
function connectionReady(err) { | ||
if (err) { | ||
debug('not able to connect to the database'); | ||
changeState('disconnected'); | ||
throw err; | ||
} | ||
self.collection = self.db.collection(collectionName); | ||
self.collection.ensureIndex({ expires: 1 }, { expireAfterSeconds: 0 }, function (err) { | ||
if (err) throw err; | ||
changeState('connected'); | ||
}); | ||
} | ||
|
||
if (options.mongoose_connection){ | ||
if (options.mongoose_connection.user && options.mongoose_connection.pass) { | ||
options.username = options.mongoose_connection.user; | ||
options.password = options.mongoose_connection.pass; | ||
function buildUrlFromOptions() { | ||
if(!options.db || typeof options.db !== 'string') { | ||
throw new Error('Required MongoStore option `db` missing'); | ||
} | ||
|
||
this.db = new mongo.Db(options.mongoose_connection.db.databaseName, | ||
new mongo.Server(options.mongoose_connection.db.serverConfig.host, | ||
options.mongoose_connection.db.serverConfig.port, | ||
options.mongoose_connection.db.serverConfig.options | ||
), | ||
{ w: options.w || defaultOptions.w }); | ||
options.url = 'mongodb://'; | ||
|
||
} else { | ||
if(!options.db) { | ||
throw new Error('Required MongoStore option `db` missing'); | ||
if (options.username) { | ||
options.url += options.username; | ||
if (options.password) options.url += ':' + options.password; | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
jdesboeufs
Author
Owner
|
||
options.url += '@'; | ||
} | ||
|
||
if (typeof options.db === 'object') { | ||
this.db = options.db; // Assume it's an instantiated DB Object | ||
} else { | ||
options.url += options.host || defaultOptions.host; | ||
options.url += ':' + (options.port || defaultOptions.port); | ||
options.url += '/' + options.db; | ||
|
||
var serverOptions = options.server || {}; | ||
serverOptions.auto_reconnect = serverOptions.auto_reconnect || options.auto_reconnect || defaultOptions.auto_reconnect; | ||
serverOptions.ssl = serverOptions.ssl || options.ssl || defaultOptions.ssl; | ||
if (options.ssl || defaultOptions.ssl) options.url += '?ssl=true'; | ||
|
||
this.db = new mongo.Db(options.db, | ||
new mongo.Server(options.host || defaultOptions.host, | ||
options.port || defaultOptions.port, | ||
serverOptions), | ||
{ w: options.w || defaultOptions.w }); | ||
if (!options.mongoOptions) { | ||
options.mongoOptions = { | ||
server: { auto_reconnect: options.auto_reconnect || defaultOptions.auto_reconnect }, | ||
db: { w: options.w || defaultOptions.w } | ||
}; | ||
} | ||
} | ||
|
||
this.db_collection_name = options.collection || defaultOptions.collection; | ||
function initWithUrl() { | ||
MongoClient.connect(options.url, options.mongoOptions || {}, function(err, db) { | ||
if (!err) self.db = db; | ||
connectionReady(err); | ||
}); | ||
} | ||
|
||
if (options.stringify || (!('stringify' in options) && !('serialize' in options) && !('unserialize' in options))) { | ||
this._serialize_session = JSON.stringify; | ||
this._unserialize_session = JSON.parse; | ||
} else { | ||
this._serialize_session = options.serialize || defaultSerializer; | ||
this._unserialize_session = options.unserialize || identity; | ||
function initWithMongooseConnection() { | ||
if (options.mongoose_connection.readyState === 1) { | ||
self.db = options.mongoose_connection.db; | ||
process.nextTick(connectionReady); | ||
} else { | ||
options.mongoose_connection.once('open', function() { | ||
self.db = options.mongoose_connection.db; | ||
connectionReady(); | ||
}); | ||
} | ||
} | ||
|
||
var self = this; | ||
function initWithNativeDb() { | ||
self.db = options.db; | ||
|
||
this._get_collection = function(callback) { | ||
if (self.collection) { | ||
callback(null, self.collection); | ||
} else if (self.db.openCalled) { | ||
self.db.collection(self.db_collection_name, function(err, collection) { | ||
if (err) { | ||
debug('not able to get collection: ' + self.db_collection_name); | ||
return callback(err); | ||
} else { | ||
self.collection = collection; | ||
|
||
// Make sure we have a TTL index on "expires", so mongod will automatically | ||
// remove expired sessions. expireAfterSeconds is set to 0 because we want | ||
// mongo to remove anything expired without any additional delay. | ||
self.collection.ensureIndex({expires: 1}, {expireAfterSeconds: 0}, function(err) { | ||
if (err) { | ||
debug('not able to set TTL index on collection: ' + self.db_collection_name); | ||
return callback(err); | ||
} | ||
|
||
callback(null, self.collection); | ||
}); | ||
} | ||
}); | ||
if (options.db.openCalled) { | ||
options.db.collection(collectionName, connectionReady); | ||
} else { | ||
self._open_database(callback); | ||
options.db.open(connectionReady); | ||
} | ||
}; | ||
|
||
this._open_database = function(cb){ | ||
self.db.open(function(err, db) { | ||
if (err) { | ||
if (!(err instanceof Error)) { err = new Error(String(err)); } | ||
err.message = 'Error connecting to database: ' + err.message; | ||
debug('not able to connect to database'); | ||
return cb(err); | ||
} | ||
} | ||
|
||
if (options.username && options.password) { | ||
db.authenticate(options.username, options.password, function () { | ||
self._get_collection(cb); | ||
this.getCollection = function (done) { | ||
switch (self.state) { | ||
case 'connected': | ||
done(null, self.collection); | ||
break; | ||
case 'connecting': | ||
self.once('connected', function () { | ||
done(null, self.collection); | ||
}); | ||
} else { | ||
self._get_collection(cb); | ||
} | ||
}); | ||
break; | ||
case 'disconnected': | ||
done(new Error('Not connected')); | ||
break; | ||
} | ||
}; | ||
|
||
this.defaultExpirationTime = options.defaultExpirationTime || defaultOptions.defaultExpirationTime; | ||
changeState('init'); | ||
|
||
if (options.url) { | ||
debug('use strategy: `url`'); | ||
initWithUrl(); | ||
} else if (options.mongoose_connection) { | ||
debug('use strategy: `mongoose_connection`'); | ||
initWithMongooseConnection(); | ||
} else if (options.db && options.db instanceof Db) { | ||
debug('use strategy: `native_db`'); | ||
initWithNativeDb(); | ||
} else { | ||
debug('use strategy: `legacy`'); | ||
buildUrlFromOptions(); | ||
initWithUrl(); | ||
} | ||
|
||
if (readyCallback) this._open_database(readyCallback); | ||
changeState('connecting'); | ||
|
||
} | ||
|
||
|
@@ -223,7 +216,7 @@ module.exports = function(connect) { | |
MongoStore.prototype.get = function(sid, callback) { | ||
sid = this.hash ? crypto.createHash(this.hash.algorithm).update(this.hash.salt + sid).digest('hex') : sid; | ||
var self = this; | ||
this._get_collection(function(err, collection) { | ||
this.getCollection(function(err, collection) { | ||
if (err) return callback(err); | ||
collection.findOne({_id: sid}, function(err, session) { | ||
if (err) { | ||
|
@@ -282,7 +275,7 @@ module.exports = function(connect) { | |
s.expires = new Date(today.getTime() + this.defaultExpirationTime); | ||
} | ||
|
||
this._get_collection(function(err, collection) { | ||
this.getCollection(function(err, collection) { | ||
if (err) return callback(err); | ||
collection.update({_id: sid}, s, {upsert: true, safe: true}, function(err) { | ||
if (err) debug('not able to set/update session: ' + sid); | ||
|
@@ -301,7 +294,7 @@ module.exports = function(connect) { | |
|
||
MongoStore.prototype.destroy = function(sid, callback) { | ||
sid = this.hash ? crypto.createHash(this.hash.algorithm).update(this.hash.salt + sid).digest('hex') : sid; | ||
this._get_collection(function(err, collection) { | ||
this.getCollection(function(err, collection) { | ||
if (err) return callback(err); | ||
collection.remove({_id: sid}, function(err) { | ||
if (err) debug('not able to destroy session: ' + sid); | ||
|
@@ -318,7 +311,7 @@ module.exports = function(connect) { | |
*/ | ||
|
||
MongoStore.prototype.length = function(callback) { | ||
this._get_collection(function(err, collection) { | ||
this.getCollection(function(err, collection) { | ||
if (err) return callback(err); | ||
collection.count({}, function(err, count) { | ||
if (err) debug('not able to count sessions'); | ||
|
@@ -335,7 +328,7 @@ module.exports = function(connect) { | |
*/ | ||
|
||
MongoStore.prototype.clear = function(callback) { | ||
this._get_collection(function(err, collection) { | ||
this.getCollection(function(err, collection) { | ||
if (err) return callback(err); | ||
collection.drop(function(err) { | ||
if (err) debug('not able to clear sessions'); | ||
|
Does this work with a blank (empty string) password? Or does it need to be
if ('password' in options)
?