@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 +2 -2
- package/src/controller/oauth.js +11 -5
- package/src/db/db.js +1 -1
- package/src/db/db.test.js +89 -25
- package/src/db/mem.js +26 -4
- package/src/db/sql/sql.js +26 -1
- package/src/env.js +1 -0
- package/src/index.js +5 -3
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.1.
|
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": {
|
package/src/controller/oauth.js
CHANGED
@@ -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
|
-
|
32
|
-
|
33
|
-
|
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:
|
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 (
|
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
|
-
|
23
|
+
ttl: 1,
|
22
24
|
},
|
23
25
|
{
|
24
26
|
engine: 'redis',
|
25
|
-
|
27
|
+
url: 'redis://localhost:6379',
|
26
28
|
},
|
27
29
|
{
|
28
30
|
engine: 'sql',
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
38
|
+
url: 'mongodb://localhost:27017/jackson',
|
37
39
|
},
|
38
40
|
{
|
39
41
|
engine: 'sql',
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
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].
|
75
|
-
dbEngine += ': ' + dbs[idx].
|
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(
|
113
|
-
const ret2 = await configStore.get(
|
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(
|
155
|
-
const ret2 = await configStore.get(
|
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(
|
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
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
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
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',
|
34
|
-
const codeStore = db.store('oauth:code',
|
35
|
-
const tokenStore = db.store('oauth:token',
|
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')({
|