@boxyhq/saml-jackson 0.2.1-beta.155 → 0.2.1-beta.161
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 +17 -14
- package/package.json +1 -1
- package/src/controller/api.js +22 -0
- package/src/db/db.js +36 -14
- package/src/db/db.test.js +72 -22
- package/src/db/encrypter.js +36 -0
- package/src/db/mem.js +1 -1
- package/src/db/mongo.js +2 -2
- package/src/db/redis.js +1 -1
- package/src/db/sql/entity/JacksonStore.js +10 -0
- package/src/db/sql/model/JacksonStore.js +3 -1
- package/src/db/sql/sql.js +15 -7
- package/src/env.js +1 -0
- package/src/index.js +3 -2
- package/src/test/api.test.js +156 -0
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
|
325
|
-
|
|
326
|
-
| HOST_URL
|
327
|
-
| HOST_PORT
|
328
|
-
| EXTERNAL_URL (npm: externalUrl)
|
329
|
-
| INTERNAL_HOST_URL
|
330
|
-
| INTERNAL_HOST_PORT
|
331
|
-
| JACKSON_API_KEYS
|
332
|
-
| SAML_AUDIENCE (npm: samlAudience)
|
333
|
-
| IDP_ENABLED (npm: idpEnabled)
|
334
|
-
| DB_ENGINE (npm: db.engine)
|
335
|
-
| DB_URL (npm: db.url)
|
336
|
-
| DB_TYPE (npm: db.type)
|
337
|
-
|
|
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
package/src/controller/api.js
CHANGED
@@ -2,6 +2,7 @@ const saml = require('../saml/saml.js');
|
|
2
2
|
const x509 = require('../saml/x509.js');
|
3
3
|
const dbutils = require('../db/utils.js');
|
4
4
|
const { indexNames } = require('./utils.js');
|
5
|
+
const { JacksonError } = require('./error.js');
|
5
6
|
|
6
7
|
const crypto = require('crypto');
|
7
8
|
|
@@ -22,6 +23,27 @@ const extractHostName = (url) => {
|
|
22
23
|
const config = async (body) => {
|
23
24
|
const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } =
|
24
25
|
body;
|
26
|
+
|
27
|
+
if (!rawMetadata) {
|
28
|
+
throw new JacksonError('Please provide rawMetadata', 400);
|
29
|
+
}
|
30
|
+
|
31
|
+
if (!defaultRedirectUrl) {
|
32
|
+
throw new JacksonError('Please provide a defaultRedirectUrl', 400);
|
33
|
+
}
|
34
|
+
|
35
|
+
if (!redirectUrl) {
|
36
|
+
throw new JacksonError('Please provide redirectUrl', 400);
|
37
|
+
}
|
38
|
+
|
39
|
+
if (!tenant) {
|
40
|
+
throw new JacksonError('Please provide tenant', 400);
|
41
|
+
}
|
42
|
+
|
43
|
+
if (!product) {
|
44
|
+
throw new JacksonError('Please provide product', 400);
|
45
|
+
}
|
46
|
+
|
25
47
|
const idpMetadata = await saml.parseMetadataAsync(rawMetadata);
|
26
48
|
|
27
49
|
// extract provider
|
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
|
-
|
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
|
-
|
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,13 +44,11 @@ class DB {
|
|
23
44
|
throw new Error('secondary indexes not allow on a store with ttl');
|
24
45
|
}
|
25
46
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
...indexes
|
32
|
-
);
|
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);
|
33
52
|
}
|
34
53
|
|
35
54
|
async delete(namespace, key) {
|
@@ -43,15 +62,18 @@ class DB {
|
|
43
62
|
|
44
63
|
module.exports = {
|
45
64
|
new: async (options) => {
|
65
|
+
const encryptionKey = options.encryptionKey
|
66
|
+
? Buffer.from(options.encryptionKey, 'latin1')
|
67
|
+
: null;
|
46
68
|
switch (options.engine) {
|
47
69
|
case 'redis':
|
48
|
-
return new DB(await redis.new(options));
|
70
|
+
return new DB(await redis.new(options), encryptionKey);
|
49
71
|
case 'sql':
|
50
|
-
return new DB(await sql.new(options));
|
72
|
+
return new DB(await sql.new(options), encryptionKey);
|
51
73
|
case 'mongo':
|
52
|
-
return new DB(await mongo.new(options));
|
74
|
+
return new DB(await mongo.new(options), encryptionKey);
|
53
75
|
case 'mem':
|
54
|
-
return new DB(await mem.new(options));
|
76
|
+
return new DB(await mem.new(options), encryptionKey);
|
55
77
|
default:
|
56
78
|
throw new Error('unsupported db engine: ' + options.engine);
|
57
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
|
-
|
23
|
-
|
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
|
-
|
27
|
-
|
87
|
+
...mongoDbConfig,
|
88
|
+
encryptionKey,
|
28
89
|
},
|
29
90
|
{
|
30
|
-
|
31
|
-
url: 'postgresql://postgres:postgres@localhost:5432/postgres',
|
32
|
-
type: 'postgres',
|
33
|
-
ttl: 1,
|
34
|
-
limit: 1,
|
91
|
+
...mysqlDbConfig,
|
35
92
|
},
|
36
93
|
{
|
37
|
-
|
38
|
-
|
94
|
+
...mysqlDbConfig,
|
95
|
+
encryptionKey,
|
39
96
|
},
|
40
97
|
{
|
41
|
-
|
42
|
-
url: 'mysql://root:mysql@localhost:3307/mysql',
|
43
|
-
type: 'mysql',
|
44
|
-
ttl: 1,
|
45
|
-
limit: 1,
|
98
|
+
...mariadbDbConfig,
|
46
99
|
},
|
47
100
|
{
|
48
|
-
|
49
|
-
|
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
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
|
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(
|
43
|
+
ret.push(doc.value);
|
44
44
|
}
|
45
45
|
|
46
46
|
return ret;
|
package/src/db/redis.js
CHANGED
package/src/db/sql/sql.js
CHANGED
@@ -14,7 +14,7 @@ class Sql {
|
|
14
14
|
while (true) {
|
15
15
|
try {
|
16
16
|
this.connection = await typeorm.createConnection({
|
17
|
-
name: options.type,
|
17
|
+
name: options.type + Math.floor(Math.random() * 100000),
|
18
18
|
type: options.type,
|
19
19
|
url: options.url,
|
20
20
|
synchronize: true,
|
@@ -39,14 +39,14 @@ class Sql {
|
|
39
39
|
this.indexRepository = this.connection.getRepository(JacksonIndex);
|
40
40
|
this.ttlRepository = this.connection.getRepository(JacksonTTL);
|
41
41
|
|
42
|
-
if (options.ttl && options.
|
42
|
+
if (options.ttl && options.cleanupLimit) {
|
43
43
|
this.ttlCleanup = async () => {
|
44
44
|
const now = Date.now();
|
45
45
|
|
46
46
|
while (true) {
|
47
47
|
const ids = await this.ttlRepository
|
48
48
|
.createQueryBuilder('jackson_ttl')
|
49
|
-
.limit(options.
|
49
|
+
.limit(options.cleanupLimit)
|
50
50
|
.where('jackson_ttl.expiresAt <= :expiresAt', { expiresAt: now })
|
51
51
|
.getMany();
|
52
52
|
|
@@ -68,7 +68,7 @@ class Sql {
|
|
68
68
|
this.timerId = setTimeout(this.ttlCleanup, options.ttl * 1000);
|
69
69
|
} else {
|
70
70
|
console.log(
|
71
|
-
'Warning: ttl cleanup not enabled, set both "ttl" and "
|
71
|
+
'Warning: ttl cleanup not enabled, set both "ttl" and "cleanupLimit" options to enable it!'
|
72
72
|
);
|
73
73
|
}
|
74
74
|
|
@@ -82,7 +82,11 @@ class Sql {
|
|
82
82
|
});
|
83
83
|
|
84
84
|
if (res && res.value) {
|
85
|
-
return
|
85
|
+
return {
|
86
|
+
value: res.value,
|
87
|
+
iv: res.iv,
|
88
|
+
tag: res.tag,
|
89
|
+
};
|
86
90
|
}
|
87
91
|
|
88
92
|
return null;
|
@@ -97,7 +101,11 @@ class Sql {
|
|
97
101
|
|
98
102
|
if (res) {
|
99
103
|
res.forEach((r) => {
|
100
|
-
ret.push(
|
104
|
+
ret.push({
|
105
|
+
value: r.store.value,
|
106
|
+
iv: r.store.iv,
|
107
|
+
tag: r.store.tag,
|
108
|
+
});
|
101
109
|
});
|
102
110
|
}
|
103
111
|
|
@@ -107,7 +115,7 @@ class Sql {
|
|
107
115
|
async put(namespace, key, val, ttl = 0, ...indexes) {
|
108
116
|
await this.connection.transaction(async (transactionalEntityManager) => {
|
109
117
|
const dbKey = dbutils.key(namespace, key);
|
110
|
-
const store = new JacksonStore(dbKey, val);
|
118
|
+
const store = new JacksonStore(dbKey, val.value, val.iv, val.tag);
|
111
119
|
await transactionalEntityManager.save(store);
|
112
120
|
|
113
121
|
if (ttl) {
|
package/src/env.js
CHANGED
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.
|
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 =
|
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 {
|
@@ -0,0 +1,156 @@
|
|
1
|
+
const tap = require('tap');
|
2
|
+
const path = require('path');
|
3
|
+
const sinon = require('sinon');
|
4
|
+
const crypto = require('crypto');
|
5
|
+
|
6
|
+
const readConfig = require('../read-config');
|
7
|
+
const dbutils = require('../db/utils');
|
8
|
+
|
9
|
+
let apiController;
|
10
|
+
|
11
|
+
const options = {
|
12
|
+
externalUrl: 'https://my-cool-app.com',
|
13
|
+
samlAudience: 'https://saml.boxyhq.com',
|
14
|
+
samlPath: '/sso/oauth/saml',
|
15
|
+
db: {
|
16
|
+
engine: 'mem',
|
17
|
+
},
|
18
|
+
};
|
19
|
+
|
20
|
+
tap.before(async () => {
|
21
|
+
const controller = await require('../index.js')(options);
|
22
|
+
|
23
|
+
apiController = controller.apiController;
|
24
|
+
});
|
25
|
+
|
26
|
+
tap.teardown(async () => {
|
27
|
+
process.exit(0);
|
28
|
+
});
|
29
|
+
|
30
|
+
tap.test('controller/api', async (t) => {
|
31
|
+
const metadataPath = path.join(__dirname, '/data/metadata');
|
32
|
+
const config = await readConfig(metadataPath);
|
33
|
+
|
34
|
+
t.test('.config()', async (t) => {
|
35
|
+
t.test('when required fields are missing or invalid', async (t) => {
|
36
|
+
t.test('when `rawMetadata` is empty', async (t) => {
|
37
|
+
const body = Object.assign({}, config[0]);
|
38
|
+
delete body['rawMetadata'];
|
39
|
+
|
40
|
+
try {
|
41
|
+
await apiController.config(body);
|
42
|
+
t.fail('Expecting JacksonError.');
|
43
|
+
} catch (err) {
|
44
|
+
t.equal(err.message, 'Please provide rawMetadata');
|
45
|
+
t.equal(err.statusCode, 400);
|
46
|
+
}
|
47
|
+
|
48
|
+
t.end();
|
49
|
+
});
|
50
|
+
|
51
|
+
t.test('when `defaultRedirectUrl` is empty', async (t) => {
|
52
|
+
const body = Object.assign({}, config[0]);
|
53
|
+
delete body['defaultRedirectUrl'];
|
54
|
+
|
55
|
+
try {
|
56
|
+
await apiController.config(body);
|
57
|
+
t.fail('Expecting JacksonError.');
|
58
|
+
} catch (err) {
|
59
|
+
t.equal(err.message, 'Please provide a defaultRedirectUrl');
|
60
|
+
t.equal(err.statusCode, 400);
|
61
|
+
}
|
62
|
+
|
63
|
+
t.end();
|
64
|
+
});
|
65
|
+
|
66
|
+
t.test('when `redirectUrl` is empty', async (t) => {
|
67
|
+
const body = Object.assign({}, config[0]);
|
68
|
+
delete body['redirectUrl'];
|
69
|
+
|
70
|
+
try {
|
71
|
+
await apiController.config(body);
|
72
|
+
t.fail('Expecting JacksonError.');
|
73
|
+
} catch (err) {
|
74
|
+
t.equal(err.message, 'Please provide redirectUrl');
|
75
|
+
t.equal(err.statusCode, 400);
|
76
|
+
}
|
77
|
+
|
78
|
+
t.end();
|
79
|
+
});
|
80
|
+
|
81
|
+
t.test('when `tenant` is empty', async (t) => {
|
82
|
+
const body = Object.assign({}, config[0]);
|
83
|
+
delete body['tenant'];
|
84
|
+
|
85
|
+
try {
|
86
|
+
await apiController.config(body);
|
87
|
+
t.fail('Expecting JacksonError.');
|
88
|
+
} catch (err) {
|
89
|
+
t.equal(err.message, 'Please provide tenant');
|
90
|
+
t.equal(err.statusCode, 400);
|
91
|
+
}
|
92
|
+
|
93
|
+
t.end();
|
94
|
+
});
|
95
|
+
|
96
|
+
t.test('when `product` is empty', async (t) => {
|
97
|
+
const body = Object.assign({}, config[0]);
|
98
|
+
delete body['product'];
|
99
|
+
|
100
|
+
try {
|
101
|
+
await apiController.config(body);
|
102
|
+
t.fail('Expecting JacksonError.');
|
103
|
+
} catch (err) {
|
104
|
+
t.equal(err.message, 'Please provide product');
|
105
|
+
t.equal(err.statusCode, 400);
|
106
|
+
}
|
107
|
+
|
108
|
+
t.end();
|
109
|
+
});
|
110
|
+
|
111
|
+
t.test('when `rawMetadata` is not a valid XML', async (t) => {
|
112
|
+
const body = Object.assign({}, config[0]);
|
113
|
+
body['rawMetadata'] = 'not a valid XML';
|
114
|
+
|
115
|
+
try {
|
116
|
+
await apiController.config(body);
|
117
|
+
t.fail('Expecting Error.');
|
118
|
+
} catch (err) {
|
119
|
+
t.match(err.message, /Non-whitespace before first tag./);
|
120
|
+
}
|
121
|
+
|
122
|
+
t.end();
|
123
|
+
});
|
124
|
+
});
|
125
|
+
|
126
|
+
t.test('when the request is good', async (t) => {
|
127
|
+
const body = Object.assign({}, config[0]);
|
128
|
+
|
129
|
+
sinon
|
130
|
+
.stub(dbutils, 'keyDigest')
|
131
|
+
.returns('75edb050796a0eb1cf2cfb0da7245f85bc50baa7');
|
132
|
+
|
133
|
+
sinon
|
134
|
+
.stub(crypto, 'randomBytes')
|
135
|
+
.returns('f3b0f91eb8f4a9f7cc2254e08682d50b05b5d36262929e7f');
|
136
|
+
|
137
|
+
const response = await apiController.config(body);
|
138
|
+
|
139
|
+
t.equal(response.client_id, '75edb050796a0eb1cf2cfb0da7245f85bc50baa7');
|
140
|
+
t.equal(
|
141
|
+
response.client_secret,
|
142
|
+
'f3b0f91eb8f4a9f7cc2254e08682d50b05b5d36262929e7f'
|
143
|
+
);
|
144
|
+
t.equal(response.provider, 'accounts.google.com');
|
145
|
+
|
146
|
+
dbutils.keyDigest.restore();
|
147
|
+
crypto.randomBytes.restore();
|
148
|
+
|
149
|
+
t.end();
|
150
|
+
});
|
151
|
+
|
152
|
+
t.end();
|
153
|
+
});
|
154
|
+
|
155
|
+
t.end();
|
156
|
+
});
|