connect-memcached 0.1.0 → 2.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/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ nodejs 18.2.0
package/Readme.md CHANGED
@@ -1,54 +1,80 @@
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
+ npm:
8
8
 
9
- ```bash
10
- $ npm install connect-memcached
9
+ ```shell
10
+ npm install connect-memcached express-session
11
+ ```
12
+
13
+ yarn:
14
+
15
+ ```shell
16
+ yarn add connect-memcached express-session
11
17
  ```
12
18
 
13
19
  ## Example
20
+
14
21
  ```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);
21
-
22
- 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']
22
+ var express = require("express"),
23
+ session = require("express-session"),
24
+ app = express(),
25
+ MemcachedStore = require("connect-memcached")(session);
26
+
27
+ app.use(
28
+ session({
29
+ secret: "CatOnKeyboard",
30
+ key: "test",
31
+ proxy: "true",
32
+ resave: false,
33
+ saveUninitialized: false,
34
+ store: new MemcachedStore({
35
+ hosts: ["127.0.0.1:11211"],
36
+ secret: "123, easy as ABC. ABC, easy as 123" // Optionally use transparent encryption for memcached session data
29
37
  })
30
- }));
31
-
32
- app.get('/', function(req, res){
33
- if(req.session.views) {
34
- ++req.session.views;
35
- } else {
36
- req.session.views = 1;
37
- }
38
- res.send('Viewed <strong>' + req.session.views + '</strong> times.');
38
+ })
39
+ );
40
+
41
+ app.get("/", function(req, res) {
42
+ if (req.session.views) {
43
+ ++req.session.views;
44
+ } else {
45
+ req.session.views = 1;
46
+ }
47
+ res.send("Viewed <strong>" + req.session.views + "</strong> times.");
39
48
  });
40
49
 
41
- http.createServer(app).listen(9341, function() {
42
- console.log("Listening on %d", this.address().port);
50
+ app.listen(9341, function() {
51
+ console.log("Listening on %d", this.address().port);
43
52
  });
44
53
  ```
45
54
 
46
55
  ## Options
47
- - `hosts` Memcached servers locations, can by string, array, hash.
48
- - `prefix` An optional prefix for each memcache key, in case you are sharing your memcached servers with something generating its own keys.
49
- - ... Rest of given option will be passed directly to the node-memcached constructor.
50
56
 
51
- For details see [node-memcached](http://github.com/3rd-Eden/node-memcached).
57
+ - `hosts` (Optional) Memcached servers locations, can be string, array or hash. Default is `127.0.0.1:11211`.
58
+ - `prefix` (Optional) Prefix for each memcached key, in case you are sharing your memcached servers with something generating its own keys.
59
+ - `ttl` (Optional) Default TTL parameter for the session data (in seconds).
60
+ - `secret` (Optional) Secret used to encrypt/decrypt session contents. Setting it enables data encryption, which is handled by [kruptein](https://github.com/jas-/kruptein) module.
61
+ - `algorithm` (Optional) Cipher algorithm from `crypto.getCiphers()`. Default is `aes-256-gcm`.
62
+ - `hashing` (Optional) Hash algorithm from `crypto.getHashes()`. Default is `sha512`.
63
+ - ... Rest of given options will be passed directly to the [node-memcached](http://github.com/3rd-Eden/node-memcached) and [kruptein](https://github.com/jas-/kruptein) constructors, see their appropriate docs for extra configurability.
64
+
65
+ ## Upgrading to v2.x.x
66
+
67
+ When upgrading from pre v2 and using data encryption please flush all the session entries from memcached before rolling the update.
68
+
69
+ Sessions without data encryption are not affected.
70
+
71
+ ## Upgrading from v0.x.x -> v1.x.x
72
+
73
+ 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**.
74
+
75
+ Upgrading library without taking appropriate action will result in `SyntaxError` exceptions during JSON parsing of decoded entries.
76
+
77
+ Sessions without encryption are not affected.
52
78
 
53
79
  ## Contributors
54
80
 
package/index.js CHANGED
@@ -1,2 +1 @@
1
-
2
- module.exports = require('./lib/connect-memcached');
1
+ module.exports = require('./lib/connect-memcached');
@@ -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,118 +21,163 @@ 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
- if (!options.client) {
36
- if (!options.hosts) {
37
- options.hosts = '127.0.0.1:11211';
38
- }
39
-
40
- options.client = new Memcached(options.hosts, options);
41
- }
42
-
43
- this.client = options.client;
44
- }
45
-
46
- MemcachedStore.prototype.__proto__ = Store.prototype;
47
-
48
- /**
49
- * Translates the given `sid` into a memcached key, optionally with prefix.
50
- *
51
- * @param {String} sid
52
- * @api private
53
- */
54
- MemcachedStore.prototype.getKey = function getKey(sid) {
55
- return this.prefix + sid;
56
- };
57
-
58
- /**
59
- * Attempt to fetch session by the given `sid`.
60
- *
61
- * @param {String} sid
62
- * @param {Function} fn
63
- * @api public
64
- */
65
- MemcachedStore.prototype.get = function(sid, fn) {
66
- sid = this.getKey(sid);
67
-
68
- this.client.get(sid, function(err, data) {
69
- if (err) { return fn(err, {}); }
70
- try {
71
- if (!data) {
72
- return fn();
73
- }
74
- fn(null, JSON.parse(data.toString()));
75
- } catch (e) {
76
- fn(e);
77
- }
78
- });
79
- };
80
-
81
- /**
82
- * Commit the given `sess` object associated with the given `sid`.
83
- *
84
- * @param {String} sid
85
- * @param {Session} sess
86
- * @param {Function} fn
87
- * @api public
88
- */
89
- MemcachedStore.prototype.set = function(sid, sess, fn) {
90
- sid = this.getKey(sid);
91
-
92
- try {
93
- var maxAge = sess.cookie.maxAge;
94
- var ttl = 'number' == typeof maxAge ? maxAge / 1000 | 0 : oneDay;
95
- var sess = JSON.stringify(sess);
96
-
97
- this.client.set(sid, sess, ttl, ensureCallback(fn));
98
- } catch (err) {
99
- fn && fn(err);
100
- }
101
- };
102
-
103
- /**
104
- * Destroy the session associated with the given `sid`.
105
- *
106
- * @param {String} sid
107
- * @param {Function} fn
108
- * @api public
109
- */
110
- MemcachedStore.prototype.destroy = function(sid, fn) {
111
- sid = this.getKey(sid);
112
- this.client.del(sid, ensureCallback(fn));
113
- };
114
-
115
- /**
116
- * Fetch number of sessions.
117
- *
118
- * @param {Function} fn
119
- * @api public
120
- */
121
- MemcachedStore.prototype.length = function(fn) {
122
- this.client.items(ensureCallback(fn));
123
- };
124
-
125
- /**
126
- * Clear all sessions.
127
- *
128
- * @param {Function} fn
129
- * @api public
130
- */
131
- MemcachedStore.prototype.clear = function(fn) {
132
- this.client.flush(ensureCallback(fn));
133
- };
134
-
135
- return MemcachedStore;
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
+
43
+ options.client = new Memcached(options.hosts, options);
44
+ }
45
+
46
+ if (options.secret) {
47
+ options.algorithm = options.algorithm || 'aes-256-gcm';
48
+ options.hashing = options.hashing || 'sha512';
49
+ this.kruptein = require("kruptein")(options);
50
+ this.secret = options.secret;
51
+ }
52
+
53
+ this.client = options.client;
54
+ }
55
+
56
+ MemcachedStore.prototype.__proto__ = Store.prototype;
57
+
58
+ /**
59
+ * Translates the given `sid` into a memcached key, optionally with prefix.
60
+ *
61
+ * @param {String} sid
62
+ * @api private
63
+ */
64
+ MemcachedStore.prototype.getKey = function getKey(sid) {
65
+ return this.prefix + sid;
66
+ };
67
+
68
+ /**
69
+ * Attempt to fetch session by the given `sid`.
70
+ *
71
+ * @param {String} sid
72
+ * @param {Function} fn
73
+ * @api public
74
+ */
75
+ MemcachedStore.prototype.get = function(sid, fn) {
76
+ var self = this, sid = this.getKey(sid),
77
+ parseable_string;
78
+
79
+ this.client.get(sid, function(err, data) {
80
+ if (err) {
81
+ return fn(err, {});
82
+ }
83
+ try {
84
+ if (!data) {
85
+ return fn();
86
+ }
87
+
88
+ if (self.secret) {
89
+ self.kruptein.get(self.secret, data, function(err, ct) {
90
+ if (err)
91
+ return fn(err, {});
92
+
93
+ parseable_string = JSON.parse(ct);
94
+ });
95
+ } else {
96
+ parseable_string = data;
97
+ }
98
+
99
+ fn(null, parseable_string);
100
+ } catch (e) {
101
+ fn(e);
102
+ }
103
+ });
104
+ };
105
+
106
+ /**
107
+ * Commit the given `sess` object associated with the given `sid`.
108
+ *
109
+ * @param {String} sid
110
+ * @param {Session} sess
111
+ * @param {Function} fn
112
+ * @api public
113
+ */
114
+ MemcachedStore.prototype.set = function(sid, sess, fn) {
115
+ sid = this.getKey(sid);
116
+
117
+ try {
118
+ var maxAge = sess.cookie.maxAge;
119
+ var ttl =
120
+ this.ttl || ("number" == typeof maxAge ? (maxAge / 1000) | 0 : oneDay);
121
+
122
+ if (this.secret) {
123
+ this.kruptein.set(this.secret, sess, function(err, ct) {
124
+ if (err)
125
+ return fn(err);
126
+
127
+ sess = ct;
128
+ });
129
+ }
130
+
131
+ this.client.set(sid, sess, ttl, ensureCallback(fn));
132
+ } catch (err) {
133
+ fn && fn(err);
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Destroy the session associated with the given `sid`.
139
+ *
140
+ * @param {String} sid
141
+ * @param {Function} fn
142
+ * @api public
143
+ */
144
+ MemcachedStore.prototype.destroy = function(sid, fn) {
145
+ sid = this.getKey(sid);
146
+ this.client.del(sid, ensureCallback(fn));
147
+ };
148
+
149
+ /**
150
+ * Fetch number of sessions.
151
+ *
152
+ * @param {Function} fn
153
+ * @api public
154
+ */
155
+ MemcachedStore.prototype.length = function(fn) {
156
+ this.client.items(ensureCallback(fn));
157
+ };
158
+
159
+ /**
160
+ * Clear all sessions.
161
+ *
162
+ * @param {Function} fn
163
+ * @api public
164
+ */
165
+ MemcachedStore.prototype.clear = function(fn) {
166
+ this.client.flush(ensureCallback(fn));
167
+ };
168
+
169
+ /**
170
+ * Refresh the time-to-live for the session with the given `sid`.
171
+ *
172
+ * @param {String} sid
173
+ * @param {Session} sess
174
+ * @param {Function} fn
175
+ * @api public
176
+ */
177
+
178
+ MemcachedStore.prototype.touch = function(sid, sess, fn) {
179
+ this.set(sid, sess, fn);
180
+ };
181
+
182
+ return MemcachedStore;
136
183
  };
package/package.json CHANGED
@@ -1,15 +1,44 @@
1
1
  {
2
- "name": "connect-memcached"
3
- , "version": "0.1.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": "2.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
+ "kruptein": "3.0.x",
20
+ "memcached": "2.2.x"
21
+ },
22
+ "devDependencies": {
23
+ "express": "^4.17.3",
24
+ "express-session": "^1.17.2",
25
+ "jest": "^27.5.1",
26
+ "supertest": "^6.2.2"
27
+ },
28
+ "scripts": {
29
+ "test": "NODE_ENV=test jest --testTimeout=10000"
30
+ },
31
+ "jest": {
32
+ "testEnvironment": "node",
33
+ "coveragePathIgnorePatterns": [
34
+ "/node_modules/"
35
+ ]
36
+ },
37
+ "engines": {
38
+ "node": ">= 0.10.0"
39
+ },
40
+ "license": "MIT",
41
+ "directories": {
42
+ "lib": "./lib"
10
43
  }
11
- , "dependencies": { "memcached": "0.2.x" }
12
- , "engines": { "node": ">=0.4.7" }
13
- , "license": "MIT"
14
- , "directories": { "lib": "./lib" }
15
44
  }
@@ -0,0 +1 @@
1
+ nodejs 17.9.0
@@ -0,0 +1,40 @@
1
+ const express = require("express"),
2
+ session = require("express-session"),
3
+ app = express(),
4
+ MemcachedStore = require("../../lib/connect-memcached")(session);
5
+
6
+ const memcachedStore = new MemcachedStore({
7
+ hosts: ["127.0.0.1:11211"],
8
+ prefix: "testapp_",
9
+ });
10
+
11
+ app.use(
12
+ session({
13
+ secret: "TestSecret",
14
+ key: "test",
15
+ proxy: "true",
16
+ resave: false,
17
+ saveUninitialized: false,
18
+ store: memcachedStore,
19
+ })
20
+ );
21
+
22
+ app.get("/", function (req, res) {
23
+ if (req.session.views) {
24
+ ++req.session.views;
25
+ } else {
26
+ req.session.views = 1;
27
+ }
28
+ res.json({ pageviews: req.session.views });
29
+ });
30
+
31
+ if (process.env.NODE_ENV !== "test") {
32
+ app.listen(9341, function () {
33
+ console.log("Listening on %d", this.address().port);
34
+ });
35
+ }
36
+
37
+ module.exports = {
38
+ app: app,
39
+ memcachedStore: memcachedStore,
40
+ };
@@ -0,0 +1,41 @@
1
+ const express = require("express"),
2
+ session = require("express-session"),
3
+ app = express(),
4
+ MemcachedStore = require("../../lib/connect-memcached")(session);
5
+
6
+ const memcachedStore = new MemcachedStore({
7
+ hosts: ["127.0.0.1:11211"],
8
+ secret: "Hello there stranger!",
9
+ prefix: "testapp_encrypt_",
10
+ });
11
+
12
+ app.use(
13
+ session({
14
+ secret: "TestEncryptSecret",
15
+ key: "test_encrypt",
16
+ proxy: "true",
17
+ resave: false,
18
+ saveUninitialized: false,
19
+ store: memcachedStore,
20
+ })
21
+ );
22
+
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.json({ pageviews: req.session.views });
30
+ });
31
+
32
+ if (process.env.NODE_ENV !== "test") {
33
+ app.listen(9341, function () {
34
+ console.log("Listening on %d", this.address().port);
35
+ });
36
+ }
37
+
38
+ module.exports = {
39
+ app: app,
40
+ memcachedStore: memcachedStore,
41
+ };
@@ -0,0 +1,44 @@
1
+ const express = require("express"),
2
+ session = require("express-session"),
3
+ app = express(),
4
+ Memcached = require("memcached"),
5
+ MemcachedStore = require("../../lib/connect-memcached")(session);
6
+
7
+ const memcachedClient = new Memcached("127.0.0.1:11211");
8
+
9
+ const memcachedStore = new MemcachedStore({
10
+ client: memcachedClient,
11
+ prefix: "testapp_encrypt_",
12
+ secret: "Hello there stranger!",
13
+ });
14
+
15
+ app.use(
16
+ session({
17
+ secret: "TestEncryptSecret",
18
+ key: "test_encrypt",
19
+ proxy: "true",
20
+ resave: false,
21
+ saveUninitialized: false,
22
+ store: memcachedStore,
23
+ })
24
+ );
25
+
26
+ app.get("/", function (req, res) {
27
+ if (req.session.views) {
28
+ ++req.session.views;
29
+ } else {
30
+ req.session.views = 1;
31
+ }
32
+ res.json({ pageviews: req.session.views });
33
+ });
34
+
35
+ if (process.env.NODE_ENV !== "test") {
36
+ app.listen(9341, function () {
37
+ console.log("Listening on %d", this.address().port);
38
+ });
39
+ }
40
+
41
+ module.exports = {
42
+ app: app,
43
+ memcachedStore: memcachedStore,
44
+ };
package/test/test.js ADDED
@@ -0,0 +1,63 @@
1
+ const supertest = require("supertest");
2
+
3
+
4
+ describe("Plain memcached session store", () => {
5
+ const { app, memcachedStore } = require("./services/memcached_basic.js");
6
+ const serverAgent = supertest.agent(app);
7
+
8
+ it("GET / should increment views value at each request", async () => {
9
+ let lastPageView = 0;
10
+ for (var i = 1; i < 50; i++) {
11
+ const res = await serverAgent.get("/");
12
+ expect(res.status).toEqual(200);
13
+ expect(res.type).toEqual(expect.stringContaining("json"));
14
+ expect(res.body.pageviews).toBeGreaterThan(lastPageView);
15
+ lastPageView = res.body.pageviews;
16
+ }
17
+ });
18
+
19
+ afterAll(() => {
20
+ memcachedStore.client.end();
21
+ });
22
+ });
23
+
24
+ describe("Encrypted memcached session store", () => {
25
+ const { app, memcachedStore } = require("./services/memcached_crypt.js");
26
+ const serverAgent = supertest.agent(app);
27
+
28
+ it("GET / should increment views value at each request", async () => {
29
+ let lastPageView = 0;
30
+ for (var i = 1; i < 50; i++) {
31
+ const res = await serverAgent.get("/");
32
+ expect(res.status).toEqual(200);
33
+ expect(res.type).toEqual(expect.stringContaining("json"));
34
+ expect(res.body.pageviews).toBeGreaterThan(lastPageView);
35
+ lastPageView = res.body.pageviews;
36
+ }
37
+ });
38
+
39
+ afterAll(() => {
40
+ memcachedStore.client.end();
41
+ });
42
+ });
43
+
44
+ describe("Encrypted session store using preexising memcached client", () => {
45
+ const { app, memcachedStore } = require("./services/memcached_preexisting_crypt_connection.js");
46
+ const serverAgent = supertest.agent(app);
47
+
48
+ it("GET / should increment views value at each request", async () => {
49
+ let lastPageView = 0;
50
+ for (var i = 1; i < 50; i++) {
51
+ const res = await serverAgent.get("/");
52
+ expect(res.status).toEqual(200);
53
+ expect(res.type).toEqual(expect.stringContaining("json"));
54
+ expect(res.body.pageviews).toBeGreaterThan(lastPageView);
55
+ expect(memcachedStore.kruptein.crypto).toBe(require("crypto"));
56
+ lastPageView = res.body.pageviews;
57
+ }
58
+ });
59
+
60
+ afterAll(() => {
61
+ memcachedStore.client.end();
62
+ });
63
+ });
package/.npmignore DELETED
@@ -1,2 +0,0 @@
1
- .idea
2
- node_modules
package/tests/test.js DELETED
@@ -1,32 +0,0 @@
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
-
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_'
16
- })
17
- }));
18
-
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.');
26
- });
27
-
28
- http.createServer(app).listen(9341, function() {
29
- console.log("Listening on %d", this.address().port);
30
- });
31
-
32
-