connect-memcached 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md CHANGED
@@ -1,59 +1,73 @@
1
1
  # connect-memcached
2
2
 
3
- Memcached session store, using [node-memcached](http://github.com/3rd-Eden/node-memcached) for communication with cache server.
3
+ Memcached session store, using [node-memcached](http://github.com/3rd-Eden/node-memcached) for communication with cache server.
4
4
 
5
5
  ## Installation
6
6
 
7
- via npm:
7
+ via npm:
8
8
 
9
9
  ```bash
10
10
  $ npm install connect-memcached
11
11
  ```
12
12
 
13
13
  ## Example
14
+
14
15
  ```javascript
15
- var express = require('express')
16
- , session = require('express-session')
17
- , cookieParser = require('cookie-parser')
18
- , http = require('http')
19
- , app = express()
20
- , MemcachedStore = require('connect-memcached')(session);
16
+ var express = require("express"),
17
+ session = require("express-session"),
18
+ cookieParser = require("cookie-parser"),
19
+ http = require("http"),
20
+ app = express(),
21
+ MemcachedStore = require("connect-memcached")(session);
21
22
 
22
23
  app.use(cookieParser());
23
- app.use(session({
24
- secret : 'CatOnKeyboard'
25
- , key : 'test'
26
- , proxy : 'true'
27
- , store : new MemcachedStore({
28
- hosts: ['127.0.0.1:11211'],
29
- secret: '123, easy as ABC. ABC, easy as 123' // Optionally use transparent encryption for memcache session data
24
+ app.use(
25
+ session({
26
+ secret: "CatOnKeyboard",
27
+ key: "test",
28
+ proxy: "true",
29
+ resave: false,
30
+ saveUninitialized: false,
31
+ store: new MemcachedStore({
32
+ hosts: ["127.0.0.1:11211"],
33
+ secret: "123, easy as ABC. ABC, easy as 123" // Optionally use transparent encryption for memcache session data
30
34
  })
31
- }));
32
-
33
- app.get('/', function(req, res){
34
- if(req.session.views) {
35
- ++req.session.views;
36
- } else {
37
- req.session.views = 1;
38
- }
39
- res.send('Viewed <strong>' + req.session.views + '</strong> times.');
35
+ })
36
+ );
37
+
38
+ app.get("/", function(req, res) {
39
+ if (req.session.views) {
40
+ ++req.session.views;
41
+ } else {
42
+ req.session.views = 1;
43
+ }
44
+ res.send("Viewed <strong>" + req.session.views + "</strong> times.");
40
45
  });
41
46
 
42
47
  http.createServer(app).listen(9341, function() {
43
- console.log("Listening on %d", this.address().port);
48
+ console.log("Listening on %d", this.address().port);
44
49
  });
45
50
  ```
46
51
 
47
52
  ## Options
53
+
48
54
  - `hosts` Memcached servers locations, can be string, array, hash.
49
55
  - `prefix` An optional prefix for each memcache key, in case you are sharing your memcached servers with something generating its own keys.
50
- - `ttl` An optional parameter used for setting the default TTL
56
+ - `ttl` An optional parameter used for setting the default TTL (in seconds)
51
57
  - `secret` An optional secret can be used to encrypt/decrypt session contents.
52
58
  - `algorithm` An optional algorithm parameter may be used, but must be valid based on returned `crypto.getCiphers()`. The current default is `aes-256-ctr` and was chosen based on the following [information](http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html)
53
- - ... Rest of given option will be passed directly to the node-memcached constructor.
59
+ - ... Rest of given option will be passed directly to the node-memcached constructor.
54
60
 
55
61
  For details see [node-memcached](http://github.com/3rd-Eden/node-memcached).
56
62
 
63
+ ## Upgrading from v0.x.x -> v1.x.x
64
+
65
+ If You're upgrading from the pre v1.0.0 version of this library and use encryption for session data be sure to **remove all session entries created with previous version**.
66
+
67
+ Upgrading library without taking appropriate action will result in `SyntaxError` exceptions during JSON parsing of decoded entries.
68
+
69
+ Sessions without encryption are not affected.
70
+
57
71
  ## Contributors
58
72
 
59
73
  Big thanks for the contributors! See the actual list [here](https://github.com/balor/connect-memcached/graphs/contributors)!
@@ -2,13 +2,15 @@
2
2
  * connect-memcached
3
3
  * MIT Licensed
4
4
  */
5
+ const bufferFrom = require('buffer-from');
5
6
 
6
- var Memcached = require('memcached');
7
+ var Memcached = require("memcached");
7
8
  var oneDay = 86400;
9
+
8
10
  function ensureCallback(fn) {
9
- return function() {
10
- fn && fn.apply(null, arguments);
11
- };
11
+ return function() {
12
+ fn && fn.apply(null, arguments);
13
+ };
12
14
  }
13
15
 
14
16
  /**
@@ -19,202 +21,221 @@ function ensureCallback(fn) {
19
21
  * @api public
20
22
  */
21
23
  module.exports = function(session) {
22
- var Store = session.Store;
23
-
24
- /**
25
- * Initialize MemcachedStore with the given `options`.
26
- *
27
- * @param {Object} options
28
- * @api public
29
- */
30
- function MemcachedStore(options) {
31
- options = options || {};
32
- Store.call(this, options);
33
-
34
- this.prefix = options.prefix || '';
35
- this.ttl = options.ttl;
36
- if (!options.client) {
37
- if (!options.hosts) {
38
- options.hosts = '127.0.0.1:11211';
39
- }
40
- if (options.secret) {
41
- this.crypto = require('crypto'),
42
- this.secret = options.secret;
43
- }
44
- if (options.algorithm) {
45
- this.algorithm = options.algorithm;
46
- }
47
-
48
- options.client = new Memcached(options.hosts, options);
49
- }
50
-
51
- this.client = options.client;
24
+ var Store = session.Store;
25
+
26
+ /**
27
+ * Initialize MemcachedStore with the given `options`.
28
+ *
29
+ * @param {Object} options
30
+ * @api public
31
+ */
32
+ function MemcachedStore(options) {
33
+ options = options || {};
34
+ Store.call(this, options);
35
+
36
+ this.prefix = options.prefix || "";
37
+ this.ttl = options.ttl;
38
+ if (!options.client) {
39
+ if (!options.hosts) {
40
+ options.hosts = "127.0.0.1:11211";
41
+ }
42
+ if (options.secret) {
43
+ (this.crypto = require("crypto")), (this.secret = options.secret);
44
+ }
45
+ if (options.algorithm) {
46
+ this.algorithm = options.algorithm;
47
+ }
48
+
49
+ options.client = new Memcached(options.hosts, options);
52
50
  }
53
51
 
54
- MemcachedStore.prototype.__proto__ = Store.prototype;
55
-
56
- /**
57
- * Translates the given `sid` into a memcached key, optionally with prefix.
58
- *
59
- * @param {String} sid
60
- * @api private
61
- */
62
- MemcachedStore.prototype.getKey = function getKey(sid) {
63
- return this.prefix + sid;
64
- };
65
-
66
- /**
67
- * Attempt to fetch session by the given `sid`.
68
- *
69
- * @param {String} sid
70
- * @param {Function} fn
71
- * @api public
72
- */
73
- MemcachedStore.prototype.get = function(sid, fn) {
74
- secret = this.secret, self = this, sid = this.getKey(sid);
75
-
76
- this.client.get(sid, function(err, data) {
77
- if (err) {
78
- return fn(err, {});
79
- }
80
- try {
81
- if (!data) {
82
- return fn();
83
- }
84
- if (secret) {
85
- parseable_string = decryptData.call(self, data.toString());
86
- }
87
- else {
88
- parseable_string = data.toString();
89
- }
90
-
91
- fn(null, JSON.parse(parseable_string));
92
- } catch (e) {
93
- fn(e);
94
- }
95
- });
96
- };
97
-
98
- /**
99
- * Commit the given `sess` object associated with the given `sid`.
100
- *
101
- * @param {String} sid
102
- * @param {Session} sess
103
- * @param {Function} fn
104
- * @api public
105
- */
106
- MemcachedStore.prototype.set = function(sid, sess, fn) {
107
- sid = this.getKey(sid);
108
-
109
- try {
110
- var maxAge = sess.cookie.maxAge;
111
- var ttl = this.ttl || ('number' == typeof maxAge ? maxAge / 1000 | 0 : oneDay);
112
- var sess = JSON.stringify((this.secret) ?
113
- encryptData.call(this, JSON.stringify(sess),
114
- this.secret, this.algorithm) :
115
- sess);
116
-
117
- this.client.set(sid, sess, ttl, ensureCallback(fn));
118
- } catch (err) {
119
- fn && fn(err);
52
+ this.client = options.client;
53
+ }
54
+
55
+ MemcachedStore.prototype.__proto__ = Store.prototype;
56
+
57
+ /**
58
+ * Translates the given `sid` into a memcached key, optionally with prefix.
59
+ *
60
+ * @param {String} sid
61
+ * @api private
62
+ */
63
+ MemcachedStore.prototype.getKey = function getKey(sid) {
64
+ return this.prefix + sid;
65
+ };
66
+
67
+ /**
68
+ * Attempt to fetch session by the given `sid`.
69
+ *
70
+ * @param {String} sid
71
+ * @param {Function} fn
72
+ * @api public
73
+ */
74
+ MemcachedStore.prototype.get = function(sid, fn) {
75
+ (secret = this.secret), (self = this), (sid = this.getKey(sid));
76
+
77
+ this.client.get(sid, function(err, data) {
78
+ if (err) {
79
+ return fn(err, {});
80
+ }
81
+ try {
82
+ if (!data) {
83
+ return fn();
120
84
  }
121
- };
122
-
123
- /**
124
- * Destroy the session associated with the given `sid`.
125
- *
126
- * @param {String} sid
127
- * @param {Function} fn
128
- * @api public
129
- */
130
- MemcachedStore.prototype.destroy = function(sid, fn) {
131
- sid = this.getKey(sid);
132
- this.client.del(sid, ensureCallback(fn));
133
- };
134
-
135
- /**
136
- * Fetch number of sessions.
137
- *
138
- * @param {Function} fn
139
- * @api public
140
- */
141
- MemcachedStore.prototype.length = function(fn) {
142
- this.client.items(ensureCallback(fn));
143
- };
144
-
145
- /**
146
- * Clear all sessions.
147
- *
148
- * @param {Function} fn
149
- * @api public
150
- */
151
- MemcachedStore.prototype.clear = function(fn) {
152
- this.client.flush(ensureCallback(fn));
153
- };
154
-
155
- /**
156
- * Refresh the time-to-live for the session with the given `sid`.
157
- *
158
- * @param {String} sid
159
- * @param {Session} sess
160
- * @param {Function} fn
161
- * @api public
162
- */
163
-
164
- MemcachedStore.prototype.touch = function (sid, sess, fn) {
165
- this.set(sid, sess, fn);
166
- }
167
-
168
-
169
- function encryptData(plaintext){
170
- var pt = encrypt.call(this, this.secret, plaintext, this.algo)
171
- , hmac = digest.call(this, this.secret, pt);
172
-
173
- return {
174
- ct: pt,
175
- mac: hmac
176
- };
177
- }
178
-
179
- function decryptData(ciphertext){
180
- ciphertext = JSON.parse(ciphertext)
181
- var hmac = digest.call(this, this.secret, ciphertext.ct);
182
-
183
- if (hmac != ciphertext.mac) {
184
- throw 'Encrypted session was tampered with!';
85
+ if (secret) {
86
+ parseable_string = decryptData.call(self, data.toString());
87
+ } else {
88
+ parseable_string = data.toString();
185
89
  }
186
90
 
187
- return decrypt.call(this, this.secret, ciphertext.ct, this.algo);
188
- }
189
-
190
- function digest(key, obj) {
191
- var hmac = this.crypto.createHmac('sha512', key);
192
- hmac.setEncoding('hex');
193
- hmac.write(obj);
194
- hmac.end();
195
- return hmac.read();
196
- }
197
-
198
- function encrypt(key, pt, algo) {
199
- algo = algo || 'aes-256-ctr';
200
- pt = (Buffer.isBuffer(pt)) ? pt : new Buffer(pt);
201
-
202
- var cipher = this.crypto.createCipher(algo, key), ct = [];
203
- ct.push(cipher.update(pt, 'buffer', 'hex'));
204
- ct.push(cipher.final('hex'));
205
-
206
- return ct.join('');
91
+ fn(null, JSON.parse(parseable_string));
92
+ } catch (e) {
93
+ fn(e);
94
+ }
95
+ });
96
+ };
97
+
98
+ /**
99
+ * Commit the given `sess` object associated with the given `sid`.
100
+ *
101
+ * @param {String} sid
102
+ * @param {Session} sess
103
+ * @param {Function} fn
104
+ * @api public
105
+ */
106
+ MemcachedStore.prototype.set = function(sid, sess, fn) {
107
+ sid = this.getKey(sid);
108
+
109
+ try {
110
+ var maxAge = sess.cookie.maxAge;
111
+ var ttl =
112
+ this.ttl || ("number" == typeof maxAge ? (maxAge / 1000) | 0 : oneDay);
113
+ var sess = JSON.stringify(
114
+ this.secret
115
+ ? encryptData.call(
116
+ this,
117
+ JSON.stringify(sess),
118
+ this.secret,
119
+ this.algorithm
120
+ )
121
+ : sess
122
+ );
123
+
124
+ this.client.set(sid, sess, ttl, ensureCallback(fn));
125
+ } catch (err) {
126
+ fn && fn(err);
207
127
  }
128
+ };
129
+
130
+ /**
131
+ * Destroy the session associated with the given `sid`.
132
+ *
133
+ * @param {String} sid
134
+ * @param {Function} fn
135
+ * @api public
136
+ */
137
+ MemcachedStore.prototype.destroy = function(sid, fn) {
138
+ sid = this.getKey(sid);
139
+ this.client.del(sid, ensureCallback(fn));
140
+ };
141
+
142
+ /**
143
+ * Fetch number of sessions.
144
+ *
145
+ * @param {Function} fn
146
+ * @api public
147
+ */
148
+ MemcachedStore.prototype.length = function(fn) {
149
+ this.client.items(ensureCallback(fn));
150
+ };
151
+
152
+ /**
153
+ * Clear all sessions.
154
+ *
155
+ * @param {Function} fn
156
+ * @api public
157
+ */
158
+ MemcachedStore.prototype.clear = function(fn) {
159
+ this.client.flush(ensureCallback(fn));
160
+ };
161
+
162
+ /**
163
+ * Refresh the time-to-live for the session with the given `sid`.
164
+ *
165
+ * @param {String} sid
166
+ * @param {Session} sess
167
+ * @param {Function} fn
168
+ * @api public
169
+ */
170
+
171
+ MemcachedStore.prototype.touch = function(sid, sess, fn) {
172
+ this.set(sid, sess, fn);
173
+ };
174
+
175
+ function encryptData(plaintext) {
176
+ var pt = encrypt.call(this, this.secret, plaintext, this.algo),
177
+ hmac = digest.call(this, this.secret, pt);
178
+
179
+ return {
180
+ ct: pt,
181
+ mac: hmac
182
+ };
183
+ }
208
184
 
209
- function decrypt(key, ct, algo) {
210
- algo = algo || 'aes-256-ctr';
211
- var cipher = this.crypto.createDecipher(algo, key), pt = [];
185
+ function decryptData(ciphertext) {
186
+ ciphertext = JSON.parse(ciphertext);
212
187
 
213
- pt.push(cipher.update(ct, 'hex', 'utf8'));
214
- pt.push(cipher.final('utf8'));
188
+ var hmac = digest.call(this, this.secret, ciphertext.ct);
215
189
 
216
- return pt.join('');
190
+ if (hmac != ciphertext.mac) {
191
+ throw "Encrypted session was tampered with!";
217
192
  }
218
193
 
219
- return MemcachedStore;
194
+ return decrypt.call(this, this.secret, ciphertext.ct, this.algo);
195
+ }
196
+
197
+ function digest(key, obj) {
198
+ var hmac = this.crypto.createHmac("sha512", key);
199
+ hmac.setEncoding("hex");
200
+ hmac.write(obj);
201
+ hmac.end();
202
+ return hmac.read();
203
+ }
204
+
205
+ function encrypt(key, pt, algo) {
206
+ algo = algo || "aes-256-ctr";
207
+ pt = Buffer.isBuffer(pt) ? pt : new bufferFrom(pt);
208
+ var iv = this.crypto.randomBytes(16);
209
+ var hashedKey = this.crypto
210
+ .createHash("sha256")
211
+ .update(key)
212
+ .digest();
213
+ var cipher = this.crypto.createCipheriv(algo, hashedKey, iv),
214
+ ct = [];
215
+ ct.push(iv.toString("hex"));
216
+ ct.push(cipher.update(pt, "buffer", "hex"));
217
+ ct.push(cipher.final("hex"));
218
+
219
+ return ct.join("");
220
+ }
221
+
222
+ function decrypt(key, ct, algo) {
223
+ algo = algo || "aes-256-ctr";
224
+ var dataBuffer = bufferFrom(ct, "hex");
225
+ var iv = dataBuffer.slice(0, 16);
226
+ var hashedKey = this.crypto
227
+ .createHash("sha256")
228
+ .update(key)
229
+ .digest();
230
+
231
+ var cipher = this.crypto.createDecipheriv(algo, hashedKey, iv),
232
+ pt = [];
233
+
234
+ pt.push(cipher.update(dataBuffer.slice(16), "hex", "utf8"));
235
+ pt.push(cipher.final("utf8"));
236
+
237
+ return pt.join("");
238
+ }
239
+
240
+ return MemcachedStore;
220
241
  };
package/package.json CHANGED
@@ -1,15 +1,28 @@
1
1
  {
2
- "name": "connect-memcached"
3
- , "version": "0.2.0"
4
- , "description": "Memcached session store for Connect"
5
- , "keywords": ["memcached", "connection", "session", "store", "cache"]
6
- , "author": "Michał Thoma <me@balor.pl>"
7
- , "repository": {
2
+ "name": "connect-memcached",
3
+ "version": "1.0.0",
4
+ "description": "Memcached session store for Connect",
5
+ "keywords": [
6
+ "memcached",
7
+ "connection",
8
+ "session",
9
+ "store",
10
+ "cache"
11
+ ],
12
+ "author": "Michał Thoma <me@balor.pl>",
13
+ "repository": {
8
14
  "type": "git",
9
15
  "url": "https://github.com/balor/connect-memcached"
16
+ },
17
+ "dependencies": {
18
+ "buffer-from": "1.1.0",
19
+ "memcached": "2.2.x"
20
+ },
21
+ "engines": {
22
+ "node": ">= 0.10.0"
23
+ },
24
+ "license": "MIT",
25
+ "directories": {
26
+ "lib": "./lib"
10
27
  }
11
- , "dependencies": { "memcached": "2.2.x" }
12
- , "engines": { "node": ">=0.4.7" }
13
- , "license": "MIT"
14
- , "directories": { "lib": "./lib" }
15
28
  }
package/tests/test.js CHANGED
@@ -1,32 +1,34 @@
1
- var express = require('express')
2
- , session = require('express-session')
3
- , cookieParser = require('cookie-parser')
4
- , http = require('http')
5
- , app = express()
6
- , MemcachedStore = require('../lib/connect-memcached')(session);
1
+ var express = require("express"),
2
+ session = require("express-session"),
3
+ cookieParser = require("cookie-parser"),
4
+ http = require("http"),
5
+ app = express(),
6
+ MemcachedStore = require("../lib/connect-memcached")(session);
7
7
 
8
8
  app.use(cookieParser());
9
- app.use(session({
10
- secret : 'TestSecret'
11
- , key : 'test'
12
- , proxy : 'true'
13
- , store : new MemcachedStore({
14
- hosts: [ '127.0.0.1:11211' ],
15
- prefix: 'testapp_'
9
+ app.use(
10
+ session({
11
+ secret: "TestSecret",
12
+ key: "test",
13
+ proxy: "true",
14
+ resave: false,
15
+ saveUninitialized: false,
16
+ store: new MemcachedStore({
17
+ hosts: ["127.0.0.1:11211"],
18
+ prefix: "testapp_"
16
19
  })
17
- }));
20
+ })
21
+ );
18
22
 
19
- app.get('/', function(req, res){
20
- if(req.session.views) {
21
- ++req.session.views;
22
- } else {
23
- req.session.views = 1;
24
- }
25
- res.send('Viewed <strong>' + req.session.views + '</strong> times.');
23
+ app.get("/", function(req, res) {
24
+ if (req.session.views) {
25
+ ++req.session.views;
26
+ } else {
27
+ req.session.views = 1;
28
+ }
29
+ res.send("Viewed <strong>" + req.session.views + "</strong> times.");
26
30
  });
27
31
 
28
32
  http.createServer(app).listen(9341, function() {
29
- console.log("Listening on %d", this.address().port);
30
- });
31
-
32
-
33
+ console.log("Listening on %d", this.address().port);
34
+ });
@@ -1,33 +1,35 @@
1
- var express = require('express')
2
- , session = require('express-session')
3
- , cookieParser = require('cookie-parser')
4
- , http = require('http')
5
- , app = express()
6
- , MemcachedStore = require('../lib/connect-memcached')(session);
1
+ var express = require("express"),
2
+ session = require("express-session"),
3
+ cookieParser = require("cookie-parser"),
4
+ http = require("http"),
5
+ app = express(),
6
+ MemcachedStore = require("../lib/connect-memcached")(session);
7
7
 
8
8
  app.use(cookieParser());
9
- app.use(session({
10
- secret : 'TestEncryptSecret'
11
- , key : 'test_encrypt'
12
- , proxy : 'true'
13
- , store : new MemcachedStore({
14
- hosts: [ '127.0.0.1:11211' ],
15
- secret: 'Keep quiet, Im working!',
16
- prefix: 'testapp_encrypt_'
9
+ app.use(
10
+ session({
11
+ secret: "TestEncryptSecret",
12
+ key: "test_encrypt",
13
+ proxy: "true",
14
+ resave: false,
15
+ saveUninitialized: false,
16
+ store: new MemcachedStore({
17
+ hosts: ["127.0.0.1:11211"],
18
+ secret: "Hello there stranger!",
19
+ prefix: "testapp_encrypt_"
17
20
  })
18
- }));
21
+ })
22
+ );
19
23
 
20
- app.get('/', function(req, res){
21
- if(req.session.views) {
22
- ++req.session.views;
23
- } else {
24
- req.session.views = 1;
25
- }
26
- res.send('Viewed <strong>' + req.session.views + '</strong> times.');
24
+ app.get("/", function(req, res) {
25
+ if (req.session.views) {
26
+ ++req.session.views;
27
+ } else {
28
+ req.session.views = 1;
29
+ }
30
+ res.send("Viewed <strong>" + req.session.views + "</strong> times.");
27
31
  });
28
32
 
29
33
  http.createServer(app).listen(9341, function() {
30
- console.log("Listening on %d", this.address().port);
34
+ console.log("Listening on %d", this.address().port);
31
35
  });
32
-
33
-