express-session-mongodb
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -106,2 +106,3 @@ //Copyright (c) 2014 Eric Vallee <eric_vallee2003@yahoo.ca> | ||
this.Filter = Options && Options.Filter; | ||
this.DeleteFlag = Options && Options.DeleteFlag ? Options.DeleteFlag : false; | ||
this.ConvertToUtf8 = Options && Options.ConvertToUtf8; | ||
@@ -189,5 +190,3 @@ this.DB = DB; | ||
{ | ||
SessionCollection.remove({'SessionID': SessionID}, function(Err, Result) { | ||
Callback(Err, null); | ||
}); | ||
Callback(null, null); | ||
} | ||
@@ -234,3 +233,3 @@ else | ||
Store.prototype.destroy = function(SessionID, Callback) { | ||
Store.prototype.FlagDeletion = function(SessionID, Callback) { | ||
var Self = this; | ||
@@ -241,8 +240,43 @@ EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
SessionCollection.remove({'SessionID': SessionID}, function(Err, Result) { | ||
HandleError(Err, Callback, function() { | ||
if(Callback) | ||
{ | ||
Callback(); | ||
} | ||
var UpdateSelector = SessionID ? {'SessionID': SessionID} : {}; | ||
var UpdateOptions = SessionID ? {'multi': false} : {'multi': true}; | ||
SessionCollection.update(UpdateSelector, {'$set': {'Delete': true}}, UpdateOptions, function(Err, Result) { | ||
Callback(Err, Result); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
Store.prototype.Cleanup = function(Callback) { | ||
var Self = this; | ||
EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
Self.DB.collection(Self.CollectionName, function(Err, SessionCollection) { | ||
HandleError(Err, Callback, function() { | ||
SessionCollection.remove({'Delete': true}, function(Err, Result) { | ||
Callback(Err, Result); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
Store.prototype.destroy = function(SessionID, Callback) { | ||
if(!this.DeleteFlag) | ||
{ | ||
var Self = this; | ||
EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
Self.DB.collection(Self.CollectionName, function(Err, SessionCollection) { | ||
HandleError(Err, Callback, function() { | ||
SessionCollection.remove({'SessionID': SessionID}, function(Err, Result) { | ||
HandleError(Err, Callback, function() { | ||
if(Callback) | ||
{ | ||
Callback(); | ||
} | ||
}); | ||
}); | ||
@@ -253,3 +287,7 @@ }); | ||
}); | ||
}); | ||
} | ||
else | ||
{ | ||
Store.prototype.FlagDeletion.call(this, SessionID, Callback); | ||
} | ||
}; | ||
@@ -263,3 +301,3 @@ | ||
HandleError(Err, Callback, function() { | ||
SessionCollection.count(function(Err, Count) { | ||
SessionCollection.count({'Delete': {'$ne': true}}, function(Err, Count) { | ||
HandleError(Err, Callback, function() { | ||
@@ -279,16 +317,19 @@ if(Callback) | ||
Store.prototype.clear = function(Callback) { | ||
var Self = this; | ||
EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
Self.DB.collection(Self.CollectionName, function(Err, SessionCollection) { | ||
//Dropping and re-creating a collection is faster than removing all elements from it | ||
Self.DependenciesOk = false; | ||
SessionCollection.drop(function(Err, Reply) { | ||
HandleError(Err, Callback, function() { | ||
EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
if(Callback) | ||
{ | ||
Callback(); | ||
} | ||
if(!this.DeleteFlag) | ||
{ | ||
var Self = this; | ||
EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
Self.DB.collection(Self.CollectionName, function(Err, SessionCollection) { | ||
//Dropping and re-creating a collection is faster than removing all elements from it | ||
Self.DependenciesOk = false; | ||
SessionCollection.drop(function(Err, Reply) { | ||
HandleError(Err, Callback, function() { | ||
EnsureDependencies.call(Self, function(Err) { | ||
HandleError(Err, Callback, function() { | ||
if(Callback) | ||
{ | ||
Callback(); | ||
} | ||
}); | ||
}); | ||
@@ -300,3 +341,7 @@ }); | ||
}); | ||
}); | ||
} | ||
else | ||
{ | ||
Store.prototype.FlagDeletion.call(this, null, Callback); | ||
} | ||
}; | ||
@@ -303,0 +348,0 @@ |
{ | ||
"name": "express-session-mongodb", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Session Store for express-session", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -82,2 +82,4 @@ ExpressSessionMongoDB | ||
- DeleteFlags: If set to true (default is false), it causes the 'destroy' (which express-session uses to delete sessions in the MongoDB store) and 'clear' calls to call 'FlagDeletion' instead of directly deleting sessions from the database. Read below for more details. | ||
<Callback> is the function that will be called when the session store instance (and its underlying database collection/index dependencies) have been created. It takes the following signature: | ||
@@ -110,2 +112,29 @@ | ||
FlagDeletion and Cleanup methods | ||
-------------------------------- | ||
In addition to the API, the 'FlagDeletion' and 'Cleanup' calls were implemented. | ||
The reason for this is to more realiably delete sessions when it matters (perhaps for security reasons). | ||
Because express-session holds sessions in memory during requests and save them back to the database before responding, physically deleting a session in the database while a request (or multiple requests) is(are) in progress will cause the said request(s) to be restore the session in the database. | ||
Feel free to look at the 'TestDeleteParallelFails' test in the integration tests for an empirical demonstration of the above phenomenon. | ||
The only way to reliably purge a session's content is to flag the session as deleted, cause the 'get' accessor to report the request as 'not found' when it is flaged as deleted and then wait for all requests holding the session in memory to complete before physically deleting the session from the database. | ||
- 'FlagDeletion' has the following signature: function(<SessionID>, <Callback>) | ||
If <SessionID> is not null, it will flag the corresponding session as deleted, else it will flag all sessions as deleted. | ||
<Callback> is called when the flagging is completed, its first argument being an error (if any is encountered, else null) and its second argument being the number of sessions that were flagged. | ||
- 'Cleanup' has the following signature: function(<Callback>) | ||
'Cleanup' deletes all sessions that are flagged as deleted from the database. It should not immediately be called after a session is flagged for deletion as sufficient time should be given for any request containing the session to complete. | ||
Alternatively, you could simply set the 'TimeToLive' option with a sufficiently big value for sessions to automatically be deleted a certain amount of time after they were last accessed. | ||
Here, <Callback> is called after the cleanup is done and contains as its first argument an error (if any, else null) and as its second argument the number of sessions that were deleted from the database. | ||
Future | ||
@@ -147,3 +176,3 @@ ====== | ||
1.1.2 | ||
1.2.0 | ||
----- | ||
@@ -155,1 +184,9 @@ | ||
- Added integration test for the above. | ||
1.3.0 | ||
----- | ||
- Changed the delete flag function not to delete on get, but merely report the session as not found | ||
- Added a method to mark a session as for deletion | ||
- Added a method to clean up sessions that are marked for deletion | ||
- Added tests and documentation for the above |
@@ -5,6 +5,7 @@ //Copyright (c) 2014 Eric Vallee <eric_vallee2003@yahoo.ca> | ||
var MongoDB = require('mongodb'); | ||
Express = require('express'); | ||
Session = require('express-session'); | ||
Store = require('../lib/ExpressSessionMongoDB'); | ||
Http = require('http'); | ||
var Express = require('express'); | ||
var Session = require('express-session'); | ||
var Store = require('../lib/ExpressSessionMongoDB'); | ||
var Http = require('http'); | ||
var Nimble = require('nimble'); | ||
@@ -43,2 +44,16 @@ var RandomIdentifier = 'ExpressSessionMongoDBTestDB'+Math.random().toString(36).slice(-8); | ||
} | ||
Context['App'].get('/Wait/:Var', function(Req, Res) { | ||
if(Req.params.Var==0) | ||
{ | ||
setImmediate(function() { | ||
Res.end(); | ||
}); | ||
} | ||
else | ||
{ | ||
setTimeout(function() { | ||
Res.end(); | ||
}, Req.params.Var); | ||
} | ||
}); | ||
Context['App'].put('/FlagDelete', function(Req, Res) { | ||
@@ -294,3 +309,3 @@ Context['Delete'] = 1; | ||
TearDown(Callback); | ||
}, | ||
}/*, | ||
'TestBefore': function(Test) { | ||
@@ -309,3 +324,3 @@ Test.expect(2); | ||
}); | ||
}, | ||
}*/, | ||
'TestObjectAPIDuring': function(Test) { | ||
@@ -399,22 +414,20 @@ Test.expect(4); | ||
exports.Delete = { | ||
'setUp': function(Callback) { | ||
Setup({'secret': 'qwerty!'}, Callback, {'TimeToLive': 2}); | ||
}, | ||
'tearDown': function(Callback) { | ||
TearDown(Callback); | ||
}, | ||
'TestMain': function(Test) { | ||
'TestDeleteGet': function(Test) { | ||
Test.expect(4); | ||
var Handler = new RequestHandler(); | ||
Context['DB'].collection('Sessions', function(Err, SessionsCollection) { | ||
Handler.Request('POST', '/Test/Increment', false, function() { | ||
var InitialSessionID = Handler.SessionID; | ||
SessionsCollection.update({}, {'$set': {'Delete': true}}, function(Err, Result) { | ||
Handler.Request('GET', '/Test', true, function(Body) { | ||
Test.ok(!Context['LastError'], "Confirming that the process was error-free."); | ||
Test.ok(Handler.SessionID != InitialSessionID, "Confirming that a new session ID has been generated."); | ||
Test.ok(!Body['Value'], "Confirming that the session data didn't survive transition."); | ||
SessionsCollection.findOne({'SessionID': InitialSessionID}, function(Err, Session) { | ||
Test.ok(!Session, "Confirming that previous session was wiped out from database."); | ||
Test.done(); | ||
Setup({'secret': 'qwerty!'}, function() { | ||
var Handler = new RequestHandler(); | ||
Context['DB'].collection('Sessions', function(Err, SessionsCollection) { | ||
Handler.Request('POST', '/Test/Increment', false, function() { | ||
var InitialSessionID = Handler.SessionID; | ||
SessionsCollection.update({}, {'$set': {'Delete': true}}, function(Err, Result) { | ||
Handler.Request('GET', '/Test', true, function(Body) { | ||
Test.ok(!Context['LastError'], "Confirming that the process was error-free."); | ||
Test.ok(Handler.SessionID != InitialSessionID, "Confirming that a new session ID has been generated."); | ||
Test.ok(!Body['Value'], "Confirming that the session data didn't survive transition."); | ||
SessionsCollection.count({}, function(Err, Amount) { | ||
Test.ok(Amount==2, "Confirming that previous session is still in the database, but flagged as deleted."); | ||
TearDown(function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
@@ -425,3 +438,65 @@ }); | ||
}); | ||
}, | ||
'TestDeleteParallelFails': function(Test) { //This is to confirm the need that FlagDeletion/Cleanup addresses | ||
Test.expect(2); | ||
Setup({'secret': 'qwerty!'}, function() { | ||
var Handler1 = new RequestHandler(); | ||
var Handler2 = new RequestHandler(); | ||
Handler1.Request('POST', '/Test/Increment', false, function() { | ||
var InitialSessionID = Handler1.SessionID; | ||
Handler2.SessionID = Handler1.SessionID; | ||
Nimble.parallel([ | ||
function(Callback) { | ||
Handler1.Request('GET', '/Wait/100', false, function() { | ||
Callback(); | ||
}); | ||
}, | ||
function(Callback) { | ||
Handler2.Request('PUT', '/Session/Destruction', false, function() { | ||
Callback(); | ||
}); | ||
}], | ||
function(Err) { | ||
Handler1.Request('GET', '/Test', true, function(Body) { | ||
Test.ok(Handler1.SessionID==InitialSessionID, "Confirming the initial session survived desipte deletion."); | ||
Test.ok(Body['Value']==1, "Confirming the initial session data survived."); | ||
TearDown(function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, | ||
'TestDeleteParallelWorks': function(Test) { //The fix, and my apologies for copy-pasting 95%+ of the code from the previous test. Got lazy. | ||
Test.expect(2); | ||
Setup({'secret': 'qwerty!'}, function() { | ||
var Handler1 = new RequestHandler(); | ||
var Handler2 = new RequestHandler(); | ||
Handler1.Request('POST', '/Test/Increment', false, function() { | ||
var InitialSessionID = Handler1.SessionID; | ||
Handler2.SessionID = Handler1.SessionID; | ||
Nimble.parallel([ | ||
function(Callback) { | ||
Handler1.Request('GET', '/Wait/100', false, function() { | ||
Callback(); | ||
}); | ||
}, | ||
function(Callback) { | ||
Handler2.Request('PUT', '/Session/Destruction', false, function() { | ||
Callback(); | ||
}); | ||
}], | ||
function(Err) { | ||
Handler1.Request('GET', '/Test', true, function(Body) { | ||
Test.ok(Handler1.SessionID!=InitialSessionID, "Confirming the initial session did not survive deletion."); | ||
Test.ok(!Body['Value'], "Confirming the initial session data did not survive."); | ||
TearDown(function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, {'DeleteFlag': true}); | ||
} | ||
}; |
@@ -181,4 +181,4 @@ //Copyright (c) 2014 Eric Vallee <eric_vallee2003@yahoo.ca> | ||
'Testsetget': function(Test) { | ||
Test.expect(24); | ||
function TestPermutation(TimeToLive, IndexSessionID, Callback) | ||
Test.expect(48); | ||
function TestPermutation(TimeToLive, IndexSessionID, DeleteFlag, Callback) | ||
{ | ||
@@ -213,9 +213,17 @@ Store(Context['DB'], function(Err, TestStore) { | ||
}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID, 'DeleteFlag': DeleteFlag}); | ||
} | ||
TestPermutation(0, false, function() { | ||
TestPermutation(100, false, function() { | ||
TestPermutation(0, true, function() { | ||
TestPermutation(100, true, function() { | ||
Test.done(); | ||
TestPermutation(0, false, false, function() { | ||
TestPermutation(100, false, false, function() { | ||
TestPermutation(0, true, false, function() { | ||
TestPermutation(100, true, false, function() { | ||
TestPermutation(0, false, true, function() { | ||
TestPermutation(100, false, true, function() { | ||
TestPermutation(0, true, true, function() { | ||
TestPermutation(100, true, true, function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -239,4 +247,4 @@ }); | ||
'Testdestroy': function(Test) { | ||
Test.expect(12); | ||
function TestPermutation(TimeToLive, IndexSessionID, Callback) | ||
Test.expect(24); | ||
function TestPermutation(TimeToLive, IndexSessionID, DeleteFlag, Callback) | ||
{ | ||
@@ -274,9 +282,17 @@ Store(Context['DB'], function(Err, TestStore) { | ||
}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID, 'DeleteFlag': DeleteFlag}); | ||
} | ||
TestPermutation(0, false, function() { | ||
TestPermutation(100, false, function() { | ||
TestPermutation(0, true, function() { | ||
TestPermutation(100, true, function() { | ||
Test.done(); | ||
TestPermutation(0, false, false, function() { | ||
TestPermutation(100, false, false, function() { | ||
TestPermutation(0, true, false, function() { | ||
TestPermutation(100, true, false, function() { | ||
TestPermutation(0, false, true, function() { | ||
TestPermutation(100, false, true, function() { | ||
TestPermutation(0, true, true, function() { | ||
TestPermutation(100, true, true, function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -288,4 +304,4 @@ }); | ||
'Testlength': function(Test) { | ||
Test.expect(20); | ||
function TestPermutation(TimeToLive, IndexSessionID, Callback) | ||
Test.expect(40); | ||
function TestPermutation(TimeToLive, IndexSessionID, DeleteFlag, Callback) | ||
{ | ||
@@ -321,9 +337,17 @@ Store(Context['DB'], function(Err, TestStore) { | ||
}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID, 'DeleteFlag': DeleteFlag}); | ||
} | ||
TestPermutation(0, false, function() { | ||
TestPermutation(100, false, function() { | ||
TestPermutation(0, true, function() { | ||
TestPermutation(100, true, function() { | ||
Test.done(); | ||
TestPermutation(0, false, false, function() { | ||
TestPermutation(100, false, false, function() { | ||
TestPermutation(0, true, false, function() { | ||
TestPermutation(100, true, false, function() { | ||
TestPermutation(0, false, true, function() { | ||
TestPermutation(100, false, true, function() { | ||
TestPermutation(0, true, true, function() { | ||
TestPermutation(100, true, true, function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -335,4 +359,4 @@ }); | ||
'Testclear': function(Test) { | ||
Test.expect(20); | ||
function TestPermutation(TimeToLive, IndexSessionID, Callback) | ||
Test.expect(40); | ||
function TestPermutation(TimeToLive, IndexSessionID, DeleteFlag, Callback) | ||
{ | ||
@@ -349,3 +373,9 @@ Store(Context['DB'], function(Err, TestStore) { | ||
ConfirmCollectionState(Indexes, Test, function() { | ||
Callback(); | ||
Context.DB.dropDatabase(function(Err, Result) { | ||
Context.DB.close(); | ||
MongoDB.MongoClient.connect("mongodb://localhost:27017/"+RandomIdentifier, {native_parser:true}, function(Err, DB) { | ||
Context['DB'] = DB; | ||
Callback(); | ||
}); | ||
}); | ||
}); | ||
@@ -358,9 +388,17 @@ }); | ||
}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID}); | ||
}, {'TimeToLive': TimeToLive, 'IndexSessionID': IndexSessionID, 'DeleteFlag': DeleteFlag}); | ||
} | ||
TestPermutation(0, false, function() { | ||
TestPermutation(100, false, function() { | ||
TestPermutation(0, true, function() { | ||
TestPermutation(100, true, function() { | ||
Test.done(); | ||
TestPermutation(0, false, false, function() { | ||
TestPermutation(100, false, false, function() { | ||
TestPermutation(0, true, false, function() { | ||
TestPermutation(100, true, false, function() { | ||
TestPermutation(0, false, true, function() { | ||
TestPermutation(100, false, true, function() { | ||
TestPermutation(0, true, true, function() { | ||
TestPermutation(100, true, true, function() { | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -370,2 +408,38 @@ }); | ||
}); | ||
}, | ||
'TestDeleteFlagAndCleanup': function(Test) { | ||
Test.expect(4); | ||
Store(Context['DB'], function(Err, TestStore) { | ||
Context['DB'].collection('Sessions', function(Err, SessionStore) { | ||
TestStore.set('a', {}, function() { | ||
TestStore.set('b', {}, function() { | ||
TestStore.set('c', {}, function() { | ||
TestStore.destroy('a', function(Err) { | ||
SessionStore.findOne({'SessionID': 'a'}, function(Err, SessionA) { | ||
Test.ok(SessionA.Delete, "Confirming that the session is flagged as deleted"); | ||
TestStore.FlagDeletion('b', function(Err) { | ||
SessionStore.findOne({'SessionID': 'b'}, function(Err, SessionB) { | ||
Test.ok(SessionB.Delete, "Confirming that the session is flagged as deleted"); | ||
TestStore.clear(function(Err) { | ||
TestStore.FlagDeletion('c', function(Err) { | ||
SessionStore.findOne({'SessionID': 'c'}, function(Err, SessionC) { | ||
Test.ok(SessionC.Delete, "Confirming that the session is flagged as deleted"); | ||
TestStore.set('d', {}, function() { | ||
TestStore.Cleanup(function(Err, Amount) { | ||
Test.ok(Amount==3, "Confirming that cleanup deleted 3 flagged sessions and left last one alone."); | ||
Test.done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, {'DeleteFlag': true}); | ||
} | ||
@@ -372,0 +446,0 @@ }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
147859
1365
189