@boxyhq/saml-jackson 0.1.3 → 0.1.4-beta.88

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.1.3",
3
+ "version": "0.1.4-beta.88",
4
4
  "license": "Apache 2.0",
5
5
  "description": "SAML 2.0 service",
6
6
  "main": "src/index.js",
@@ -20,7 +20,7 @@
20
20
  "calendso": "cross-env DB_URL=postgresql://postgres:postgres@localhost:5450/calendso nodemon src/jackson.js",
21
21
  "mongo": "cross-env DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson nodemon src/jackson.js",
22
22
  "pre-loaded": "cross-env DB_ENGINE=mem PRE_LOADED_CONFIG='./_config' nodemon src/jackson.js",
23
- "test": "tap src/**/*.test.js",
23
+ "test": "tap --timeout=100 src/**/*.test.js",
24
24
  "dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d"
25
25
  },
26
26
  "tap": {
@@ -28,10 +28,16 @@ const extractBearerToken = (req) => {
28
28
  function getEncodedClientId(client_id) {
29
29
  try {
30
30
  const sp = new URLSearchParams(client_id);
31
- return {
32
- tenant: sp.get('tenant'),
33
- product: sp.get('product'),
34
- };
31
+ const tenant = sp.get('tenant');
32
+ const product = sp.get('product');
33
+ if (tenant && product) {
34
+ return {
35
+ tenant: sp.get('tenant'),
36
+ product: sp.get('product'),
37
+ };
38
+ }
39
+
40
+ return null;
35
41
  } catch (err) {
36
42
  return null;
37
43
  }
@@ -287,7 +293,7 @@ const token = async (req, res) => {
287
293
  res.json({
288
294
  access_token: token,
289
295
  token_type: 'bearer',
290
- expires_in: 300,
296
+ expires_in: options.db.ttl,
291
297
  });
292
298
  };
293
299
 
package/src/db/db.js CHANGED
@@ -19,7 +19,7 @@ class DB {
19
19
 
20
20
  // ttl is in seconds
21
21
  async put(namespace, key, val, ttl = 0, ...indexes) {
22
- if (this.ttl > 0 && indexes && indexes.length > 0) {
22
+ if (ttl > 0 && indexes && indexes.length > 0) {
23
23
  throw new Error('secondary indexes not allow on a store with ttl');
24
24
  }
25
25
 
package/src/db/db.test.js CHANGED
@@ -3,6 +3,8 @@ const t = require('tap');
3
3
  const DB = require('./db.js');
4
4
 
5
5
  let configStores = [];
6
+ let ttlStores = [];
7
+ const ttl = 3;
6
8
 
7
9
  const record1 = {
8
10
  id: '1',
@@ -18,48 +20,46 @@ const record2 = {
18
20
  const dbs = [
19
21
  {
20
22
  engine: 'mem',
21
- options: {},
23
+ ttl: 1,
22
24
  },
23
25
  {
24
26
  engine: 'redis',
25
- options: { url: 'redis://localhost:6379' },
27
+ url: 'redis://localhost:6379',
26
28
  },
27
29
  {
28
30
  engine: 'sql',
29
- options: {
30
- url: 'postgresql://postgres:postgres@localhost:5432/postgres',
31
- type: 'postgres',
32
- },
31
+ url: 'postgresql://postgres:postgres@localhost:5432/postgres',
32
+ type: 'postgres',
33
+ ttl: 1,
34
+ limit: 1,
33
35
  },
34
36
  {
35
37
  engine: 'mongo',
36
- options: { url: 'mongodb://localhost:27017/jackson' },
38
+ url: 'mongodb://localhost:27017/jackson',
37
39
  },
38
40
  {
39
41
  engine: 'sql',
40
- options: {
41
- url: 'mysql://root:mysql@localhost:3307/mysql',
42
- type: 'mysql',
43
- },
42
+ url: 'mysql://root:mysql@localhost:3307/mysql',
43
+ type: 'mysql',
44
+ ttl: 1,
45
+ limit: 1,
44
46
  },
45
47
  {
46
48
  engine: 'sql',
47
- options: {
48
- url: 'mariadb://root@localhost:3306/mysql',
49
- type: 'mariadb',
50
- },
49
+ url: 'mariadb://root@localhost:3306/mysql',
50
+ type: 'mariadb',
51
+ ttl: 1,
52
+ limit: 1,
51
53
  },
52
54
  ];
53
55
 
54
56
  t.before(async () => {
55
57
  for (const idx in dbs) {
56
- const config = dbs[idx];
57
- const engine = config.engine;
58
- const opts = config.options;
59
- opts.engine = engine;
58
+ const opts = dbs[idx];
60
59
  const db = await DB.new(opts);
61
60
 
62
61
  configStores.push(db.store('saml:config'));
62
+ ttlStores.push(db.store('oauth:session', ttl));
63
63
  }
64
64
  });
65
65
 
@@ -70,9 +70,10 @@ t.teardown(async () => {
70
70
  t.test('dbs', ({ end }) => {
71
71
  for (const idx in configStores) {
72
72
  const configStore = configStores[idx];
73
+ const ttlStore = ttlStores[idx];
73
74
  let dbEngine = dbs[idx].engine;
74
- if (dbs[idx].options.type) {
75
- dbEngine += ': ' + dbs[idx].options.type;
75
+ if (dbs[idx].type) {
76
+ dbEngine += ': ' + dbs[idx].type;
76
77
  }
77
78
  t.test('put(): ' + dbEngine, async (t) => {
78
79
  await configStore.put(
@@ -109,8 +110,8 @@ t.test('dbs', ({ end }) => {
109
110
  });
110
111
 
111
112
  t.test('get(): ' + dbEngine, async (t) => {
112
- const ret1 = await configStore.get('1');
113
- const ret2 = await configStore.get('2');
113
+ const ret1 = await configStore.get(record1.id);
114
+ const ret2 = await configStore.get(record2.id);
114
115
 
115
116
  t.same(ret1, record1, 'unable to get record1');
116
117
  t.same(ret2, record2, 'unable to get record2');
@@ -151,8 +152,8 @@ t.test('dbs', ({ end }) => {
151
152
 
152
153
  await configStore.delete(record2.id);
153
154
 
154
- const ret1 = await configStore.get('1');
155
- const ret2 = await configStore.get('2');
155
+ const ret1 = await configStore.get(record1.id);
156
+ const ret2 = await configStore.get(record2.id);
156
157
 
157
158
  const ret3 = await configStore.getByIndex({
158
159
  name: 'name',
@@ -171,6 +172,69 @@ t.test('dbs', ({ end }) => {
171
172
 
172
173
  t.end();
173
174
  });
175
+
176
+ t.test('ttl indexes: ' + dbEngine, async (t) => {
177
+ try {
178
+ await ttlStore.put(
179
+ record1.id,
180
+ record1,
181
+ {
182
+ // secondary index on city
183
+ name: 'city',
184
+ value: record1.city,
185
+ },
186
+ {
187
+ // secondary index on name
188
+ name: 'name',
189
+ value: record1.name,
190
+ }
191
+ );
192
+
193
+ t.fail('expecting a secondary indexes not allow on a store with ttl');
194
+ } catch (err) {
195
+ t.ok(err, 'got expected error');
196
+ }
197
+
198
+ t.end();
199
+ });
200
+
201
+ t.test('ttl put(): ' + dbEngine, async (t) => {
202
+ await ttlStore.put(record1.id, record1);
203
+
204
+ await ttlStore.put(record2.id, record2);
205
+
206
+ t.end();
207
+ });
208
+
209
+ t.test('ttl get(): ' + dbEngine, async (t) => {
210
+ const ret1 = await ttlStore.get(record1.id);
211
+ const ret2 = await ttlStore.get(record2.id);
212
+
213
+ t.same(ret1, record1, 'unable to get record1');
214
+ t.same(ret2, record2, 'unable to get record2');
215
+
216
+ t.end();
217
+ });
218
+
219
+ t.test('ttl expiry: ' + dbEngine, async (t) => {
220
+ // mongo runs ttl task every 60 seconds
221
+ if (dbEngine.startsWith('mongo')) {
222
+ t.end();
223
+ return;
224
+ }
225
+
226
+ await new Promise((resolve) =>
227
+ setTimeout(resolve, ((dbEngine === 'mem' ? 5 : 0) + ttl + 0.5) * 1000)
228
+ );
229
+
230
+ const ret1 = await ttlStore.get(record1.id);
231
+ const ret2 = await ttlStore.get(record2.id);
232
+
233
+ t.same(ret1, null, 'ttl for record1 failed');
234
+ t.same(ret2, null, 'ttl for record2 failed');
235
+
236
+ t.end();
237
+ });
174
238
  }
175
239
 
176
240
  t.test('db.new() error', async (t) => {
package/src/db/mem.js CHANGED
@@ -2,14 +2,32 @@
2
2
  const dbutils = require('./utils.js');
3
3
 
4
4
  class Mem {
5
- constructor(/*options*/) {
5
+ constructor(options) {
6
6
  return (async () => {
7
7
  this.store = {}; // map of key, value
8
8
  this.indexes = {}; // map of key, Set
9
9
  this.cleanup = {}; // map of indexes for cleanup when store key is deleted
10
10
  this.ttlStore = {}; // map of key to ttl
11
11
 
12
- return this; // Return the newly-created instance
12
+ if (options.ttl) {
13
+ this.ttlCleanup = async () => {
14
+ const now = Date.now();
15
+ for (const k in this.ttlStore) {
16
+ if (this.ttlStore[k].expiresAt < now) {
17
+ await this.delete(
18
+ this.ttlStore[k].namespace,
19
+ this.ttlStore[k].key
20
+ );
21
+ }
22
+ }
23
+
24
+ this.timerId = setTimeout(this.ttlCleanup, options.ttl * 1000);
25
+ };
26
+
27
+ this.timerId = setTimeout(this.ttlCleanup, options.ttl * 1000);
28
+ }
29
+
30
+ return this;
13
31
  })();
14
32
  }
15
33
 
@@ -39,8 +57,11 @@ class Mem {
39
57
  this.store[k] = JSON.stringify(val);
40
58
 
41
59
  if (ttl) {
42
- // TODO: implement cleanup of TTL
43
- this.ttlStore[k] = Date.now() + ttl * 1000;
60
+ this.ttlStore[k] = {
61
+ namespace,
62
+ key,
63
+ expiresAt: Date.now() + ttl * 1000,
64
+ };
44
65
  }
45
66
 
46
67
  // no ttl support for secondary indexes
@@ -79,6 +100,7 @@ class Mem {
79
100
  }
80
101
 
81
102
  delete this.cleanup[idxKey];
103
+ delete this.ttlStore[k];
82
104
  }
83
105
  }
84
106
 
package/src/db/sql/sql.js CHANGED
@@ -23,7 +23,32 @@ class Sql {
23
23
  this.storeRepository = this.connection.getRepository(JacksonStore);
24
24
  this.indexRepository = this.connection.getRepository(JacksonIndex);
25
25
 
26
- return this; // Return the newly-created instance
26
+ if (options.ttl && options.limit) {
27
+ this.ttlCleanup = async () => {
28
+ const now = Date.now();
29
+
30
+ while (true) {
31
+ const ids = await this.storeRepository.find({
32
+ expiresAt: typeorm.MoreThan(now),
33
+ take: options.limit,
34
+ });
35
+
36
+ if (ids.length <= 0) {
37
+ break;
38
+ }
39
+
40
+ await this.storeRepository.remove(ids);
41
+ }
42
+
43
+ this.timerId = setTimeout(this.ttlCleanup, options.ttl * 1000);
44
+ };
45
+
46
+ this.timerId = setTimeout(this.ttlCleanup, options.ttl * 1000);
47
+ } else {
48
+ console.log('Warning: ttl cleanup not enabled, set both "ttl" and "limit" options to enable it!')
49
+ }
50
+
51
+ return this;
27
52
  })();
28
53
  }
29
54
 
package/src/env.js CHANGED
@@ -15,6 +15,7 @@ const db = {
15
15
  engine: process.env.DB_ENGINE,
16
16
  url: process.env.DB_URL,
17
17
  type: process.env.DB_TYPE,
18
+ ttl: process.env.DB_TTL,
18
19
  };
19
20
 
20
21
  module.exports = {
package/src/index.js CHANGED
@@ -21,6 +21,8 @@ const defaultOpts = (opts) => {
21
21
  newOpts.db.url =
22
22
  newOpts.db.url || 'postgres://postgres:postgres@localhost:5432/jackson';
23
23
  newOpts.db.type = newOpts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql. Supported values: postgres, cockroachdb, mysql, mariadb
24
+ newOpts.db.ttl = (newOpts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
25
+ newOpts.db.limit = (newOpts.db.limit || 1000) * 1; // Limit ttl cleanup to this many items at a time
24
26
 
25
27
  return newOpts;
26
28
  };
@@ -30,9 +32,9 @@ module.exports = async function (opts) {
30
32
 
31
33
  const db = await DB.new(opts.db);
32
34
  const configStore = db.store('saml:config');
33
- const sessionStore = db.store('oauth:session', 300);
34
- const codeStore = db.store('oauth:code', 300);
35
- const tokenStore = db.store('oauth:token', 300);
35
+ const sessionStore = db.store('oauth:session', opts.db.ttl);
36
+ const codeStore = db.store('oauth:code', opts.db.ttl);
37
+ const tokenStore = db.store('oauth:token', opts.db.ttl);
36
38
 
37
39
  const apiController = require('./controller/api.js')({ configStore });
38
40
  const oauthController = require('./controller/oauth.js')({