@boxyhq/saml-jackson 0.2.1-beta.152 → 0.2.1-beta.157

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
@@ -321,20 +321,23 @@ Configuration is done via env vars (and in the case of the npm library via an op
321
321
 
322
322
  The following options are supported and will have to be configured during deployment.
323
323
 
324
- | Key | Description | Default |
325
- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
326
- | HOST_URL | The URL to bind to | `localhost` |
327
- | HOST_PORT | The port to bind to | `5000` |
328
- | EXTERNAL_URL (npm: externalUrl) | The public URL to reach this service, used internally for documenting the SAML configuration instructions. | `http://{HOST_URL}:{HOST_PORT}` |
329
- | INTERNAL_HOST_URL | The URL to bind to expose the internal APIs. Do not configure this to a public network. | `localhost` |
330
- | INTERNAL_HOST_PORT | The port to bind to for the internal APIs. | `6000` |
331
- | JACKSON_API_KEYS | A comma separated list of API keys that will be validated when serving the Config API requests | |
332
- | SAML_AUDIENCE (npm: samlAudience) | This is just an identifier to validate the SAML audience, this value will also get configured in the SAML apps created by your customers. Once set do not change this value unless you get your customers to reconfigure their SAML again. It is case-sensitive. This does not have to be a real URL. | `https://saml.boxyhq.com` |
333
- | IDP_ENABLED (npm: idpEnabled) | Set to `true` to enable IdP initiated login for SAML. SP initiated login is the only recommended flow but you might have to support IdP login at times. | `false` |
334
- | DB_ENGINE (npm: db.engine) | Supported values are `redis`, `sql`, `mongo`, `mem`. | `sql` |
335
- | DB_URL (npm: db.url) | The database URL to connect to. For example `postgres://postgres:postgres@localhost:5450/jackson` | |
336
- | DB_TYPE (npm: db.type) | Only needed when DB_ENGINE is `sql`. Supported values are `postgres`, `cockroachdb`, `mysql`, `mariadb`. | `postgres` |
337
- | PRE_LOADED_CONFIG | If you only need a single tenant or a handful of pre-configured tenants then this config will help you read and load SAML configs. It works well with the mem DB engine so you don't have to configure any external databases for this to work (though it works with those as well). This is a path (absolute or relative) to a directory that contains files organized in the format described in the next section. | |
324
+ | Key | Description | Default |
325
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
326
+ | HOST_URL | The URL to bind to | `localhost` |
327
+ | HOST_PORT | The port to bind to | `5000` |
328
+ | EXTERNAL_URL (npm: externalUrl) | The public URL to reach this service, used internally for documenting the SAML configuration instructions. | `http://{HOST_URL}:{HOST_PORT}` |
329
+ | INTERNAL_HOST_URL | The URL to bind to expose the internal APIs. Do not configure this to a public network. | `localhost` |
330
+ | INTERNAL_HOST_PORT | The port to bind to for the internal APIs. | `6000` |
331
+ | JACKSON_API_KEYS | A comma separated list of API keys that will be validated when serving the Config API requests | |
332
+ | SAML_AUDIENCE (npm: samlAudience) | This is just an identifier to validate the SAML audience, this value will also get configured in the SAML apps created by your customers. Once set do not change this value unless you get your customers to reconfigure their SAML again. It is case-sensitive. This does not have to be a real URL. | `https://saml.boxyhq.com` |
333
+ | IDP_ENABLED (npm: idpEnabled) | Set to `true` to enable IdP initiated login for SAML. SP initiated login is the only recommended flow but you might have to support IdP login at times. | `false` |
334
+ | DB_ENGINE (npm: db.engine) | Supported values are `redis`, `sql`, `mongo`, `mem`. | `sql` |
335
+ | DB_URL (npm: db.url) | The database URL to connect to. For example `postgres://postgres:postgres@localhost:5450/jackson` | |
336
+ | DB_TYPE (npm: db.type) | Only needed when DB_ENGINE is `sql`. Supported values are `postgres`, `cockroachdb`, `mysql`, `mariadb`. | `postgres` |
337
+ | DB_TTL (npm: db.ttl) | TTL for the code, session and token stores (in seconds). | 300 |
338
+ | DB_CLEANUP_LIMIT (npm: db.cleanupLimit) | Limit cleanup of TTL entries to this number. | 1000 |
339
+ | DB_ENCRYPTION_KEY (npm: db.encryptionKey) | To encrypt data at rest specify a 32 character key. | |
340
+ | PRE_LOADED_CONFIG | If you only need a single tenant or a handful of pre-configured tenants then this config will help you read and load SAML configs. It works well with the mem DB engine so you don't have to configure any external databases for this to work (though it works with those as well). This is a path (absolute or relative) to a directory that contains files organized in the format described in the next section. | |
338
341
 
339
342
  ## Pre-loaded SAML Configuration
340
343
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.2.1-beta.152",
3
+ "version": "0.2.1-beta.157",
4
4
  "license": "Apache 2.0",
5
5
  "description": "SAML 2.0 service",
6
6
  "main": "src/index.js",
@@ -10,7 +10,7 @@ let configStore;
10
10
  const extractHostName = (url) => {
11
11
  try {
12
12
  const pUrl = new URL(url);
13
- if(pUrl.hostname.startsWith('www.')) {
13
+ if (pUrl.hostname.startsWith('www.')) {
14
14
  return pUrl.hostname.substring(4);
15
15
  }
16
16
  return pUrl.hostname;
@@ -56,7 +56,7 @@ const config = async (body) => {
56
56
  {
57
57
  idpMetadata,
58
58
  defaultRedirectUrl,
59
- redirectUrl: JSON.parse(redirectUrl),
59
+ redirectUrl: JSON.parse(redirectUrl), // redirectUrl is a stringified array
60
60
  tenant,
61
61
  product,
62
62
  clientID,
package/src/db/db.js CHANGED
@@ -3,18 +3,39 @@ const mongo = require('./mongo.js');
3
3
  const redis = require('./redis.js');
4
4
  const sql = require('./sql/sql.js');
5
5
  const store = require('./store.js');
6
+ const encrypter = require('./encrypter.js');
7
+
8
+ const decrypt = (res, encryptionKey) => {
9
+ if (res.iv && res.tag) {
10
+ return JSON.parse(
11
+ encrypter.decrypt(res.value, res.iv, res.tag, encryptionKey)
12
+ );
13
+ }
14
+
15
+ return JSON.parse(res.value);
16
+ };
6
17
 
7
18
  class DB {
8
- constructor(db) {
19
+ constructor(db, encryptionKey) {
9
20
  this.db = db;
21
+ this.encryptionKey = encryptionKey;
10
22
  }
11
23
 
12
24
  async get(namespace, key) {
13
- return await this.db.get(namespace, key);
25
+ const res = await this.db.get(namespace, key);
26
+ if (!res) {
27
+ return null;
28
+ }
29
+
30
+ return decrypt(res, this.encryptionKey);
14
31
  }
15
32
 
16
33
  async getByIndex(namespace, idx) {
17
- return await this.db.getByIndex(namespace, idx);
34
+ const res = await this.db.getByIndex(namespace, idx);
35
+ const encryptionKey = this.encryptionKey;
36
+ return res.map((r) => {
37
+ return decrypt(r, encryptionKey);
38
+ });
18
39
  }
19
40
 
20
41
  // ttl is in seconds
@@ -23,7 +44,11 @@ class DB {
23
44
  throw new Error('secondary indexes not allow on a store with ttl');
24
45
  }
25
46
 
26
- return await this.db.put(namespace, key, val, ttl, ...indexes);
47
+ const dbVal = this.encryptionKey
48
+ ? encrypter.encrypt(JSON.stringify(val), this.encryptionKey)
49
+ : { value: JSON.stringify(val) };
50
+
51
+ return await this.db.put(namespace, key, dbVal, ttl, ...indexes);
27
52
  }
28
53
 
29
54
  async delete(namespace, key) {
@@ -37,15 +62,18 @@ class DB {
37
62
 
38
63
  module.exports = {
39
64
  new: async (options) => {
65
+ const encryptionKey = options.encryptionKey
66
+ ? Buffer.from(options.encryptionKey, 'latin1')
67
+ : null;
40
68
  switch (options.engine) {
41
69
  case 'redis':
42
- return new DB(await redis.new(options));
70
+ return new DB(await redis.new(options), encryptionKey);
43
71
  case 'sql':
44
- return new DB(await sql.new(options));
72
+ return new DB(await sql.new(options), encryptionKey);
45
73
  case 'mongo':
46
- return new DB(await mongo.new(options));
74
+ return new DB(await mongo.new(options), encryptionKey);
47
75
  case 'mem':
48
- return new DB(await mem.new(options));
76
+ return new DB(await mem.new(options), encryptionKey);
49
77
  default:
50
78
  throw new Error('unsupported db engine: ' + options.engine);
51
79
  }
package/src/db/db.test.js CHANGED
@@ -2,6 +2,8 @@ const t = require('tap');
2
2
 
3
3
  const DB = require('./db.js');
4
4
 
5
+ const encryptionKey = '3yGrTcnKPBqqHoH3zZMAU6nt4bmIYb2q';
6
+
5
7
  let configStores = [];
6
8
  let ttlStores = [];
7
9
  const ttl = 3;
@@ -17,39 +19,87 @@ const record2 = {
17
19
  city: 'London',
18
20
  };
19
21
 
22
+ const memDbConfig = {
23
+ engine: 'mem',
24
+ ttl: 1,
25
+ };
26
+
27
+ const redisDbConfig = {
28
+ engine: 'redis',
29
+ url: 'redis://localhost:6379',
30
+ };
31
+
32
+ const postgresDbConfig = {
33
+ engine: 'sql',
34
+ url: 'postgresql://postgres:postgres@localhost:5432/postgres',
35
+ type: 'postgres',
36
+ ttl: 1,
37
+ cleanupLimit: 1,
38
+ };
39
+
40
+ const mongoDbConfig = {
41
+ engine: 'mongo',
42
+ url: 'mongodb://localhost:27017/jackson',
43
+ };
44
+
45
+ const mysqlDbConfig = {
46
+ engine: 'sql',
47
+ url: 'mysql://root:mysql@localhost:3307/mysql',
48
+ type: 'mysql',
49
+ ttl: 1,
50
+ cleanupLimit: 1,
51
+ };
52
+
53
+ const mariadbDbConfig = {
54
+ engine: 'sql',
55
+ url: 'mariadb://root@localhost:3306/mysql',
56
+ type: 'mariadb',
57
+ ttl: 1,
58
+ cleanupLimit: 1,
59
+ };
60
+
20
61
  const dbs = [
21
62
  {
22
- engine: 'mem',
23
- ttl: 1,
63
+ ...memDbConfig,
64
+ },
65
+ {
66
+ ...memDbConfig,
67
+ encryptionKey,
68
+ },
69
+ {
70
+ ...redisDbConfig,
71
+ },
72
+ {
73
+ ...redisDbConfig,
74
+ encryptionKey,
75
+ },
76
+ {
77
+ ...postgresDbConfig,
78
+ },
79
+ {
80
+ ...postgresDbConfig,
81
+ encryptionKey,
82
+ },
83
+ {
84
+ ...mongoDbConfig,
24
85
  },
25
86
  {
26
- engine: 'redis',
27
- url: 'redis://localhost:6379',
87
+ ...mongoDbConfig,
88
+ encryptionKey,
28
89
  },
29
90
  {
30
- engine: 'sql',
31
- url: 'postgresql://postgres:postgres@localhost:5432/postgres',
32
- type: 'postgres',
33
- ttl: 1,
34
- limit: 1,
91
+ ...mysqlDbConfig,
35
92
  },
36
93
  {
37
- engine: 'mongo',
38
- url: 'mongodb://localhost:27017/jackson',
94
+ ...mysqlDbConfig,
95
+ encryptionKey,
39
96
  },
40
97
  {
41
- engine: 'sql',
42
- url: 'mysql://root:mysql@localhost:3307/mysql',
43
- type: 'mysql',
44
- ttl: 1,
45
- limit: 1,
98
+ ...mariadbDbConfig,
46
99
  },
47
100
  {
48
- engine: 'sql',
49
- url: 'mariadb://root@localhost:3306/mysql',
50
- type: 'mariadb',
51
- ttl: 1,
52
- limit: 1,
101
+ ...mariadbDbConfig,
102
+ encryptionKey,
53
103
  },
54
104
  ];
55
105
 
@@ -224,7 +274,7 @@ t.test('dbs', ({ end }) => {
224
274
  }
225
275
 
226
276
  await new Promise((resolve) =>
227
- setTimeout(resolve, (2*ttl + 0.5) * 1000)
277
+ setTimeout(resolve, (2 * ttl + 0.5) * 1000)
228
278
  );
229
279
 
230
280
  const ret1 = await ttlStore.get(record1.id);
@@ -0,0 +1,36 @@
1
+ const crypto = require('crypto');
2
+
3
+ const ALGO = 'aes-256-gcm';
4
+ const BLOCK_SIZE = 16; // 128 bit
5
+
6
+ const encrypt = (text, key) => {
7
+ const iv = crypto.randomBytes(BLOCK_SIZE);
8
+ const cipher = crypto.createCipheriv(ALGO, key, iv);
9
+
10
+ let ciphertext = cipher.update(text, 'utf8', 'base64');
11
+ ciphertext += cipher.final('base64');
12
+ return {
13
+ iv: iv.toString('base64'),
14
+ tag: cipher.getAuthTag().toString('base64'),
15
+ value: ciphertext,
16
+ };
17
+ };
18
+
19
+ const decrypt = (ciphertext, iv, tag, key) => {
20
+ const decipher = crypto.createDecipheriv(
21
+ ALGO,
22
+ key,
23
+ Buffer.from(iv, 'base64')
24
+ );
25
+ decipher.setAuthTag(Buffer.from(tag, 'base64'));
26
+
27
+ let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
28
+ cleartext += decipher.final('utf8');
29
+
30
+ return cleartext;
31
+ };
32
+
33
+ module.exports = {
34
+ encrypt,
35
+ decrypt,
36
+ };
package/src/db/mem.js CHANGED
@@ -34,7 +34,7 @@ class Mem {
34
34
  async get(namespace, key) {
35
35
  let res = this.store[dbutils.key(namespace, key)];
36
36
  if (res) {
37
- return JSON.parse(res);
37
+ return res;
38
38
  }
39
39
 
40
40
  return null;
@@ -54,7 +54,7 @@ class Mem {
54
54
  async put(namespace, key, val, ttl = 0, ...indexes) {
55
55
  const k = dbutils.key(namespace, key);
56
56
 
57
- this.store[k] = JSON.stringify(val);
57
+ this.store[k] = val;
58
58
 
59
59
  if (ttl) {
60
60
  this.ttlStore[k] = {
package/src/db/mongo.js CHANGED
@@ -25,7 +25,7 @@ class Mongo {
25
25
  _id: dbutils.key(namespace, key),
26
26
  });
27
27
  if (res && res.value) {
28
- return JSON.parse(res.value);
28
+ return res.value;
29
29
  }
30
30
 
31
31
  return null;
@@ -40,7 +40,7 @@ class Mongo {
40
40
 
41
41
  const ret = [];
42
42
  for (const doc of docs || []) {
43
- ret.push(JSON.parse(doc.value));
43
+ ret.push(doc.value);
44
44
  }
45
45
 
46
46
  return ret;
@@ -48,7 +48,7 @@ class Mongo {
48
48
 
49
49
  async put(namespace, key, val, ttl = 0, ...indexes) {
50
50
  const doc = {
51
- value: JSON.stringify(val),
51
+ value: val,
52
52
  };
53
53
 
54
54
  if (ttl) {
@@ -27,6 +27,16 @@ module.exports = (type) => {
27
27
  value: {
28
28
  type: valueType(type),
29
29
  },
30
+ iv: {
31
+ type: 'varchar',
32
+ length: 64,
33
+ nullable: true,
34
+ },
35
+ tag: {
36
+ type: 'varchar',
37
+ length: 64,
38
+ nullable: true,
39
+ },
30
40
  },
31
41
  });
32
42
  };
@@ -1,7 +1,9 @@
1
1
  /*export */ class JacksonStore {
2
- constructor(key, value) {
2
+ constructor(key, value, iv, tag) {
3
3
  this.key = key;
4
4
  this.value = value;
5
+ this.iv = iv;
6
+ this.tag = tag;
5
7
  }
6
8
  }
7
9
 
package/src/db/sql/sql.js CHANGED
@@ -1,3 +1,5 @@
1
+ /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
2
+
1
3
  require('reflect-metadata');
2
4
  const typeorm = require('typeorm');
3
5
  const JacksonStore = require('./model/JacksonStore.js');
@@ -12,7 +14,7 @@ class Sql {
12
14
  while (true) {
13
15
  try {
14
16
  this.connection = await typeorm.createConnection({
15
- name: options.type,
17
+ name: options.type + Math.floor(Math.random() * 100000),
16
18
  type: options.type,
17
19
  url: options.url,
18
20
  synchronize: true,
@@ -37,14 +39,14 @@ class Sql {
37
39
  this.indexRepository = this.connection.getRepository(JacksonIndex);
38
40
  this.ttlRepository = this.connection.getRepository(JacksonTTL);
39
41
 
40
- if (options.ttl && options.limit) {
42
+ if (options.ttl && options.cleanupLimit) {
41
43
  this.ttlCleanup = async () => {
42
44
  const now = Date.now();
43
45
 
44
46
  while (true) {
45
47
  const ids = await this.ttlRepository
46
48
  .createQueryBuilder('jackson_ttl')
47
- .limit(options.limit)
49
+ .limit(options.cleanupLimit)
48
50
  .where('jackson_ttl.expiresAt <= :expiresAt', { expiresAt: now })
49
51
  .getMany();
50
52
 
@@ -66,7 +68,7 @@ class Sql {
66
68
  this.timerId = setTimeout(this.ttlCleanup, options.ttl * 1000);
67
69
  } else {
68
70
  console.log(
69
- 'Warning: ttl cleanup not enabled, set both "ttl" and "limit" options to enable it!'
71
+ 'Warning: ttl cleanup not enabled, set both "ttl" and "cleanupLimit" options to enable it!'
70
72
  );
71
73
  }
72
74
 
@@ -79,8 +81,12 @@ class Sql {
79
81
  key: dbutils.key(namespace, key),
80
82
  });
81
83
 
82
- if (res) {
83
- return JSON.parse(res.value);
84
+ if (res && res.value) {
85
+ return {
86
+ value: res.value,
87
+ iv: res.iv,
88
+ tag: res.tag,
89
+ };
84
90
  }
85
91
 
86
92
  return null;
@@ -95,21 +101,21 @@ class Sql {
95
101
 
96
102
  if (res) {
97
103
  res.forEach((r) => {
98
- ret.push(JSON.parse(r.store.value));
104
+ ret.push({
105
+ value: r.store.value,
106
+ iv: r.store.iv,
107
+ tag: r.store.tag,
108
+ });
99
109
  });
100
110
  }
101
111
 
102
- if (res && res.store) {
103
- return JSON.parse(res.store.value);
104
- }
105
-
106
112
  return ret;
107
113
  }
108
114
 
109
115
  async put(namespace, key, val, ttl = 0, ...indexes) {
110
116
  await this.connection.transaction(async (transactionalEntityManager) => {
111
117
  const dbKey = dbutils.key(namespace, key);
112
- const store = new JacksonStore(dbKey, JSON.stringify(val));
118
+ const store = new JacksonStore(dbKey, val.value, val.iv, val.tag);
113
119
  await transactionalEntityManager.save(store);
114
120
 
115
121
  if (ttl) {
package/src/env.js CHANGED
@@ -18,6 +18,7 @@ const db = {
18
18
  url: process.env.DB_URL,
19
19
  type: process.env.DB_TYPE,
20
20
  ttl: process.env.DB_TTL,
21
+ encryptionKey: process.env.DB_ENCRYPTION_KEY,
21
22
  };
22
23
 
23
24
  module.exports = {
package/src/index.js CHANGED
@@ -22,7 +22,7 @@ const defaultOpts = (opts) => {
22
22
  newOpts.db.url || 'postgresql://postgres:postgres@localhost:5432/postgres';
23
23
  newOpts.db.type = newOpts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql. Supported values: postgres, cockroachdb, mysql, mariadb
24
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
25
+ newOpts.db.cleanupLimit = (newOpts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
26
26
 
27
27
  return newOpts;
28
28
  };
@@ -56,7 +56,8 @@ module.exports = async function (opts) {
56
56
  }
57
57
  }
58
58
 
59
- const type = opts.db.engine === 'sql' && opts.db.type ? ' Type: ' + opts.db.type : '';
59
+ const type =
60
+ opts.db.engine === 'sql' && opts.db.type ? ' Type: ' + opts.db.type : '';
60
61
  console.log(`Using engine: ${opts.db.engine}.${type}`);
61
62
 
62
63
  return {