@boxyhq/saml-jackson 0.2.4 → 0.3.0-beta.246
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/Dockerfile +9 -7
- package/README.md +1 -2
- package/dist/controller/api.d.ts +32 -0
- package/dist/controller/api.js +193 -0
- package/dist/controller/error.d.ts +5 -0
- package/dist/controller/error.js +12 -0
- package/dist/controller/oauth/allowed.d.ts +1 -0
- package/dist/controller/oauth/allowed.js +17 -0
- package/dist/controller/oauth/code-verifier.d.ts +2 -0
- package/dist/controller/oauth/code-verifier.js +15 -0
- package/dist/controller/oauth/redirect.d.ts +1 -0
- package/dist/controller/oauth/redirect.js +11 -0
- package/dist/controller/oauth.d.ts +23 -0
- package/dist/controller/oauth.js +263 -0
- package/dist/controller/utils.d.ts +6 -0
- package/dist/controller/utils.js +17 -0
- package/dist/db/db.d.ts +15 -0
- package/dist/db/db.js +107 -0
- package/dist/db/encrypter.d.ts +3 -0
- package/dist/db/encrypter.js +29 -0
- package/dist/db/mem.d.ts +20 -0
- package/dist/db/mem.js +128 -0
- package/dist/db/mongo.d.ts +17 -0
- package/dist/db/mongo.js +106 -0
- package/dist/db/redis.d.ts +15 -0
- package/dist/db/redis.js +107 -0
- package/dist/db/sql/entity/JacksonIndex.d.ts +7 -0
- package/dist/db/sql/entity/JacksonIndex.js +41 -0
- package/dist/db/sql/entity/JacksonStore.d.ts +6 -0
- package/dist/db/sql/entity/JacksonStore.js +42 -0
- package/dist/db/sql/entity/JacksonTTL.d.ts +4 -0
- package/dist/db/sql/entity/JacksonTTL.js +29 -0
- package/dist/db/sql/sql.d.ts +20 -0
- package/dist/db/sql/sql.js +174 -0
- package/dist/db/store.d.ts +5 -0
- package/dist/db/store.js +68 -0
- package/dist/db/utils.d.ts +7 -0
- package/dist/db/utils.js +29 -0
- package/dist/env.d.ts +22 -0
- package/dist/env.js +35 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +80 -0
- package/dist/jackson.d.ts +1 -0
- package/dist/jackson.js +153 -0
- package/dist/read-config.d.ts +3 -0
- package/dist/read-config.js +50 -0
- package/dist/saml/claims.d.ts +6 -0
- package/dist/saml/claims.js +35 -0
- package/dist/saml/saml.d.ts +11 -0
- package/dist/saml/saml.js +200 -0
- package/dist/saml/x509.d.ts +7 -0
- package/dist/saml/x509.js +69 -0
- package/dist/typings.d.ts +137 -0
- package/dist/typings.js +2 -0
- package/package.json +41 -21
- package/.dockerignore +0 -2
- package/.eslintrc.js +0 -13
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -43
- package/.github/pull_request_template.md +0 -31
- package/.github/workflows/codesee-arch-diagram.yml +0 -81
- package/.github/workflows/main.yml +0 -123
- package/_dev/docker-compose.yml +0 -37
- package/map.js +0 -1
- package/prettier.config.js +0 -4
- package/src/controller/api.js +0 -167
- package/src/controller/error.js +0 -12
- package/src/controller/oauth/allowed.js +0 -19
- package/src/controller/oauth/code-verifier.js +0 -16
- package/src/controller/oauth/redirect.js +0 -18
- package/src/controller/oauth.js +0 -321
- package/src/controller/utils.js +0 -19
- package/src/db/db.js +0 -81
- package/src/db/db.test.js +0 -302
- package/src/db/encrypter.js +0 -36
- package/src/db/mem.js +0 -111
- package/src/db/mongo.js +0 -89
- package/src/db/redis.js +0 -88
- package/src/db/sql/entity/JacksonIndex.js +0 -42
- package/src/db/sql/entity/JacksonStore.js +0 -42
- package/src/db/sql/entity/JacksonTTL.js +0 -23
- package/src/db/sql/model/JacksonIndex.js +0 -9
- package/src/db/sql/model/JacksonStore.js +0 -10
- package/src/db/sql/model/JacksonTTL.js +0 -8
- package/src/db/sql/sql.js +0 -153
- package/src/db/store.js +0 -42
- package/src/db/utils.js +0 -30
- package/src/env.js +0 -39
- package/src/index.js +0 -67
- package/src/jackson.js +0 -161
- package/src/read-config.js +0 -24
- package/src/saml/claims.js +0 -40
- package/src/saml/saml.js +0 -223
- package/src/saml/x509.js +0 -48
- package/src/test/api.test.js +0 -186
- package/src/test/data/metadata/boxyhq.js +0 -6
- package/src/test/data/metadata/boxyhq.xml +0 -30
- package/src/test/data/saml_response +0 -1
- package/src/test/oauth.test.js +0 -342
package/src/test/api.test.js
DELETED
@@ -1,186 +0,0 @@
|
|
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 CLIENT_ID = '75edb050796a0eb1cf2cfb0da7245f85bc50baa7';
|
12
|
-
const PROVIDER = 'accounts.google.com';
|
13
|
-
const OPTIONS = {
|
14
|
-
externalUrl: 'https://my-cool-app.com',
|
15
|
-
samlAudience: 'https://saml.boxyhq.com',
|
16
|
-
samlPath: '/sso/oauth/saml',
|
17
|
-
db: {
|
18
|
-
engine: 'mem',
|
19
|
-
},
|
20
|
-
};
|
21
|
-
|
22
|
-
tap.before(async () => {
|
23
|
-
const controller = await require('../index.js')(OPTIONS);
|
24
|
-
|
25
|
-
apiController = controller.apiController;
|
26
|
-
});
|
27
|
-
|
28
|
-
tap.teardown(async () => {
|
29
|
-
process.exit(0);
|
30
|
-
});
|
31
|
-
|
32
|
-
tap.test('controller/api', async (t) => {
|
33
|
-
const metadataPath = path.join(__dirname, '/data/metadata');
|
34
|
-
const config = await readConfig(metadataPath);
|
35
|
-
|
36
|
-
t.test('.config()', async (t) => {
|
37
|
-
t.test('when required fields are missing or invalid', async (t) => {
|
38
|
-
t.test('when `rawMetadata` is empty', async (t) => {
|
39
|
-
const body = Object.assign({}, config[0]);
|
40
|
-
delete body['rawMetadata'];
|
41
|
-
|
42
|
-
try {
|
43
|
-
await apiController.config(body);
|
44
|
-
t.fail('Expecting JacksonError.');
|
45
|
-
} catch (err) {
|
46
|
-
t.equal(err.message, 'Please provide rawMetadata');
|
47
|
-
t.equal(err.statusCode, 400);
|
48
|
-
}
|
49
|
-
|
50
|
-
t.end();
|
51
|
-
});
|
52
|
-
|
53
|
-
t.test('when `defaultRedirectUrl` is empty', async (t) => {
|
54
|
-
const body = Object.assign({}, config[0]);
|
55
|
-
delete body['defaultRedirectUrl'];
|
56
|
-
|
57
|
-
try {
|
58
|
-
await apiController.config(body);
|
59
|
-
t.fail('Expecting JacksonError.');
|
60
|
-
} catch (err) {
|
61
|
-
t.equal(err.message, 'Please provide a defaultRedirectUrl');
|
62
|
-
t.equal(err.statusCode, 400);
|
63
|
-
}
|
64
|
-
|
65
|
-
t.end();
|
66
|
-
});
|
67
|
-
|
68
|
-
t.test('when `redirectUrl` is empty', async (t) => {
|
69
|
-
const body = Object.assign({}, config[0]);
|
70
|
-
delete body['redirectUrl'];
|
71
|
-
|
72
|
-
try {
|
73
|
-
await apiController.config(body);
|
74
|
-
t.fail('Expecting JacksonError.');
|
75
|
-
} catch (err) {
|
76
|
-
t.equal(err.message, 'Please provide redirectUrl');
|
77
|
-
t.equal(err.statusCode, 400);
|
78
|
-
}
|
79
|
-
|
80
|
-
t.end();
|
81
|
-
});
|
82
|
-
|
83
|
-
t.test('when `tenant` is empty', async (t) => {
|
84
|
-
const body = Object.assign({}, config[0]);
|
85
|
-
delete body['tenant'];
|
86
|
-
|
87
|
-
try {
|
88
|
-
await apiController.config(body);
|
89
|
-
t.fail('Expecting JacksonError.');
|
90
|
-
} catch (err) {
|
91
|
-
t.equal(err.message, 'Please provide tenant');
|
92
|
-
t.equal(err.statusCode, 400);
|
93
|
-
}
|
94
|
-
|
95
|
-
t.end();
|
96
|
-
});
|
97
|
-
|
98
|
-
t.test('when `product` is empty', async (t) => {
|
99
|
-
const body = Object.assign({}, config[0]);
|
100
|
-
delete body['product'];
|
101
|
-
|
102
|
-
try {
|
103
|
-
await apiController.config(body);
|
104
|
-
t.fail('Expecting JacksonError.');
|
105
|
-
} catch (err) {
|
106
|
-
t.equal(err.message, 'Please provide product');
|
107
|
-
t.equal(err.statusCode, 400);
|
108
|
-
}
|
109
|
-
|
110
|
-
t.end();
|
111
|
-
});
|
112
|
-
|
113
|
-
t.test('when `rawMetadata` is not a valid XML', async (t) => {
|
114
|
-
const body = Object.assign({}, config[0]);
|
115
|
-
body['rawMetadata'] = 'not a valid XML';
|
116
|
-
|
117
|
-
try {
|
118
|
-
await apiController.config(body);
|
119
|
-
t.fail('Expecting Error.');
|
120
|
-
} catch (err) {
|
121
|
-
t.match(err.message, /Non-whitespace before first tag./);
|
122
|
-
}
|
123
|
-
|
124
|
-
t.end();
|
125
|
-
});
|
126
|
-
});
|
127
|
-
|
128
|
-
t.test('when the request is good', async (t) => {
|
129
|
-
const body = Object.assign({}, config[0]);
|
130
|
-
|
131
|
-
const kdStub = sinon.stub(dbutils, 'keyDigest').returns(CLIENT_ID);
|
132
|
-
const rbStub = sinon
|
133
|
-
.stub(crypto, 'randomBytes')
|
134
|
-
.returns('f3b0f91eb8f4a9f7cc2254e08682d50b05b5d36262929e7f');
|
135
|
-
|
136
|
-
const response = await apiController.config(body);
|
137
|
-
t.ok(kdStub.called);
|
138
|
-
t.ok(rbStub.calledOnce);
|
139
|
-
t.equal(response.client_id, CLIENT_ID);
|
140
|
-
t.equal(
|
141
|
-
response.client_secret,
|
142
|
-
'f3b0f91eb8f4a9f7cc2254e08682d50b05b5d36262929e7f'
|
143
|
-
);
|
144
|
-
t.equal(response.provider, PROVIDER);
|
145
|
-
|
146
|
-
let savedConf = await apiController.getConfig({
|
147
|
-
clientID: CLIENT_ID,
|
148
|
-
});
|
149
|
-
t.equal(savedConf.provider, PROVIDER);
|
150
|
-
try {
|
151
|
-
await apiController.deleteConfig({ clientID: CLIENT_ID });
|
152
|
-
t.fail('Expecting JacksonError.');
|
153
|
-
} catch (err) {
|
154
|
-
t.equal(err.message, 'Please provide clientSecret');
|
155
|
-
t.equal(err.statusCode, 400);
|
156
|
-
}
|
157
|
-
try {
|
158
|
-
await apiController.deleteConfig({
|
159
|
-
clientID: CLIENT_ID,
|
160
|
-
clientSecret: 'xxxxx',
|
161
|
-
});
|
162
|
-
t.fail('Expecting JacksonError.');
|
163
|
-
} catch (err) {
|
164
|
-
t.equal(err.message, 'clientSecret mismatch');
|
165
|
-
t.equal(err.statusCode, 400);
|
166
|
-
}
|
167
|
-
await apiController.deleteConfig({
|
168
|
-
clientID: CLIENT_ID,
|
169
|
-
clientSecret: 'f3b0f91eb8f4a9f7cc2254e08682d50b05b5d36262929e7f',
|
170
|
-
});
|
171
|
-
savedConf = await apiController.getConfig({
|
172
|
-
clientID: CLIENT_ID,
|
173
|
-
});
|
174
|
-
t.same(savedConf, {}, 'should return empty config');
|
175
|
-
|
176
|
-
dbutils.keyDigest.restore();
|
177
|
-
crypto.randomBytes.restore();
|
178
|
-
|
179
|
-
t.end();
|
180
|
-
});
|
181
|
-
|
182
|
-
t.end();
|
183
|
-
});
|
184
|
-
|
185
|
-
t.end();
|
186
|
-
});
|
@@ -1,30 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
-
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://accounts.google.com/o/saml2" validUntil="2026-06-22T18:39:53.000Z">
|
3
|
-
<md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
4
|
-
<md:KeyDescriptor use="signing">
|
5
|
-
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
6
|
-
<ds:X509Data>
|
7
|
-
<ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAXo6K+u/MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
|
8
|
-
bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
|
9
|
-
b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjEwNjIz
|
10
|
-
MTgzOTUzWhcNMjYwNjIyMTgzOTUzWjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
|
11
|
-
TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
|
12
|
-
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
13
|
-
MIIBCgKCAQEA4qZcxwPiVka9GzGdQ9LVlgVkn3A7O3HtxR6RIm5AMaL4YZziEHt2HgxLdJZyXYJw
|
14
|
-
yfT1KB2IHt+XDQBkgEpQVXuuwSPI8vhI8Jr+nr8zia3MMoy9vJF8ZG7HuWeakaKEh7tJqjYu1Cl9
|
15
|
-
a81rkYdXAFUA+gl2q+stvK26xylAUwptCJSQo0NanWzCq+k5zvX0uLmh58+W5Yv11hDTtAoW+1dH
|
16
|
-
LWUTHXPfoZINPRy5NGKJ2Onq5/D5XJRimNnUa2iYi0Yv9txp1RRq4dpB9MaVttt3iKyDo4/+8fg/
|
17
|
-
bL8BLhguiOeqcP4DEIzMuExi3bZAOu2NC7k7Qf28nA81LzP9DQIDAQABMA0GCSqGSIb3DQEBCwUA
|
18
|
-
A4IBAQARBNB3+MfmKr5WXNXXE9YwUzUGmpfbqUPXh2y2dOAkj6TzoekAsOLWB0p8oyJ5d1bFlTsx
|
19
|
-
i1OY9RuFl0tc35Jbo+ae5GfUvJmbnYGi9z8sBL55HY6x3KQNmM/ehof7ttZwvB6nwuRxAiGYG497
|
20
|
-
3tSzrqMQzEskcgX1mlCW0vks/ztCaayprDXcCUxWdP9FaiSZDEXV6PHhFZgGlRNvERsgaMDJgOsq
|
21
|
-
v6hLX10Q9CtOWzqu18PI4DcfoZ7exWcC29yWvwZzDTfHGaSG1DtUFLwiQmhVUbfd7/fmLV+/iOxV
|
22
|
-
zI0b5xSYZOJ7Kena7gd5zGVrc2ygKAFKiffiI5GLmLkv</ds:X509Certificate>
|
23
|
-
</ds:X509Data>
|
24
|
-
</ds:KeyInfo>
|
25
|
-
</md:KeyDescriptor>
|
26
|
-
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
|
27
|
-
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://accounts.google.com/o/saml2"/>
|
28
|
-
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://accounts.google.com/o/saml2"/>
|
29
|
-
</md:IDPSSODescriptor>
|
30
|
-
</md:EntityDescriptor>
|
@@ -1 +0,0 @@
|
|
1
|
-
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzYW1sMnA6UmVzcG9uc2UgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIERlc3RpbmF0aW9uPSJodHRwczovLzdlYTItMTAzLTE1My0xMDQtMTYxLm5ncm9rLmlvL3Nzby9vYXV0aC9zYW1sIiBJRD0iXzRkZmM5MjYwZDFjZTVlMDRhZTQ4ZTg4ZWJkNGNlOTY3IiBJblJlc3BvbnNlVG89Il9kYWNkMTRhZGVmMmNiMDc1NGM5NiIgSXNzdWVJbnN0YW50PSIyMDIxLTEyLTA2VDE1OjIzOjA3LjM2MFoiIFZlcnNpb249IjIuMCI+DQogICA8c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9zYW1sMjwvc2FtbDI6SXNzdWVyPg0KICAgPGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogICAgICA8ZHM6U2lnbmVkSW5mbz4NCiAgICAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIiAvPg0KICAgICAgICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiIC8+DQogICAgICAgICA8ZHM6UmVmZXJlbmNlIFVSST0iI180ZGZjOTI2MGQxY2U1ZTA0YWU0OGU4OGViZDRjZTk2NyI+DQogICAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIgLz4NCiAgICAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiIC8+DQogICAgICAgICAgICA8L2RzOlRyYW5zZm9ybXM+DQogICAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2IiAvPg0KICAgICAgICAgICAgPGRzOkRpZ2VzdFZhbHVlPjI3Vy9EZTVKTlZIWkk1VTVKZGIvUi9mUXIya0pmd1VPbk0vVlRmQ1ZwQU09PC9kczpEaWdlc3RWYWx1ZT4NCiAgICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPm4vWCsvb0ZzK3JNU2gxMlg4dnowWlNkNlFpcU50eTY3RnFVbVNwdGllRmNoM0NScGQzZ2dpSHNwTTlMcm9MWjZVZGlsbThtdFZqTkgNCi9ob21yWDNvVEQ4UStxdzNiSllOTEptNEQvRWNmZmRDNmpSb0RJZzRYeFYxYlBVaWhQTnI4dlBFZEF2eEdwNTNiZ2MyREJsWkJpT3gNCnh4RlBSbEtnajJDWjh5SWk1R05FMUVTYms2SEtjY3g4R2dxWmtSYkVRbWtnbVMxZG1xcGl5bUpQM3orMHlaaXQyZ3dwQW5WVjNCMDUNCnZOcy8rSTFzRlZLaHNWcTc4QzZNWlZzV1pUSi80RFhadWhVSnpLNElTSU11b1RqUWFacEZMeEpBKzlhZzIvcm9OMjkwcitpcTZ5MVQNCjNHSlF1TlBPU0JVS1NpWlZVNmdwTldRRDBxckFxSWFQUFpOZnp3PT08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgICA8ZHM6WDUwOURhdGE+DQogICAgICAgICAgICA8ZHM6WDUwOVN1YmplY3ROYW1lPlNUPUNhbGlmb3JuaWEsQz1VUyxPVT1Hb29nbGUgRm9yIFdvcmssQ049R29vZ2xlLEw9TW91bnRhaW4gVmlldyxPPUdvb2dsZSBJbmMuPC9kczpYNTA5U3ViamVjdE5hbWU+DQogICAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURkRENDQWx5Z0F3SUJBZ0lHQVhvNksrdS9NQTBHQ1NxR1NJYjNEUUVCQ3dVQU1Ic3hGREFTQmdOVkJBb1RDMGR2YjJkc1pTQkoNCmJtTXVNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01ROHdEUVlEVlFRREV3WkhiMjluYkdVeEdEQVdCZ05WQkFzVEQwZHYNCmIyZHNaU0JHYjNJZ1YyOXlhekVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXdIaGNOTWpFd05qSXoNCk1UZ3pPVFV6V2hjTk1qWXdOakl5TVRnek9UVXpXakI3TVJRd0VnWURWUVFLRXd0SGIyOW5iR1VnU1c1akxqRVdNQlFHQTFVRUJ4TU4NClRXOTFiblJoYVc0Z1ZtbGxkekVQTUEwR0ExVUVBeE1HUjI5dloyeGxNUmd3RmdZRFZRUUxFdzlIYjI5bmJHVWdSbTl5SUZkdmNtc3gNCkN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEENCk1JSUJDZ0tDQVFFQTRxWmN4d1BpVmthOUd6R2RROUxWbGdWa24zQTdPM0h0eFI2UkltNUFNYUw0WVp6aUVIdDJIZ3hMZEpaeVhZSncNCnlmVDFLQjJJSHQrWERRQmtnRXBRVlh1dXdTUEk4dmhJOEpyK25yOHppYTNNTW95OXZKRjhaRzdIdVdlYWthS0VoN3RKcWpZdTFDbDkNCmE4MXJrWWRYQUZVQStnbDJxK3N0dksyNnh5bEFVd3B0Q0pTUW8wTmFuV3pDcStrNXp2WDB1TG1oNTgrVzVZdjExaERUdEFvVysxZEgNCkxXVVRIWFBmb1pJTlBSeTVOR0tKMk9ucTUvRDVYSlJpbU5uVWEyaVlpMFl2OXR4cDFSUnE0ZHBCOU1hVnR0dDNpS3lEbzQvKzhmZy8NCmJMOEJMaGd1aU9lcWNQNERFSXpNdUV4aTNiWkFPdTJOQzdrN1FmMjhuQTgxTHpQOURRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUENCkE0SUJBUUFSQk5CMytNZm1LcjVXWE5YWEU5WXdVelVHbXBmYnFVUFhoMnkyZE9Ba2o2VHpvZWtBc09MV0IwcDhveUo1ZDFiRmxUc3gNCmkxT1k5UnVGbDB0YzM1SmJvK2FlNUdmVXZKbWJuWUdpOXo4c0JMNTVIWTZ4M0tRTm1NL2Vob2Y3dHRad3ZCNm53dVJ4QWlHWUc0OTcNCjN0U3pycU1RekVza2NnWDFtbENXMHZrcy96dENhYXlwckRYY0NVeFdkUDlGYWlTWkRFWFY2UEhoRlpnR2xSTnZFUnNnYU1ESmdPc3ENCnY2aExYMTBROUN0T1d6cXUxOFBJNERjZm9aN2V4V2NDMjl5V3Z3WnpEVGZIR2FTRzFEdFVGTHdpUW1oVlViZmQ3L2ZtTFYrL2lPeFYNCnpJMGI1eFNZWk9KN0tlbmE3Z2Q1ekdWcmMyeWdLQUZLaWZmaUk1R0xtTGt2PC9kczpYNTA5Q2VydGlmaWNhdGU+DQogICAgICAgICA8L2RzOlg1MDlEYXRhPg0KICAgICAgPC9kczpLZXlJbmZvPg0KICAgPC9kczpTaWduYXR1cmU+DQogICA8c2FtbDJwOlN0YXR1cz4NCiAgICAgIDxzYW1sMnA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIiAvPg0KICAgPC9zYW1sMnA6U3RhdHVzPg0KICAgPHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il8xMWZkN2U5MDdjZmNiMTEwODM3NjI5OGM1Nzc0ZjgyNyIgSXNzdWVJbnN0YW50PSIyMDIxLTEyLTA2VDE1OjIzOjA3LjM2MFoiIFZlcnNpb249IjIuMCI+DQogICAgICA8c2FtbDI6SXNzdWVyPmh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL3NhbWwyPC9zYW1sMjpJc3N1ZXI+DQogICAgICA8c2FtbDI6U3ViamVjdD4NCiAgICAgICAgIDxzYW1sMjpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPmtpcmFuQGRlbW8uY29tPC9zYW1sMjpOYW1lSUQ+DQogICAgICAgICA8c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICAgICAgPHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIEluUmVzcG9uc2VUbz0iX2RhY2QxNGFkZWYyY2IwNzU0Yzk2IiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMTItMDZUMTU6Mjg6MDcuMzYwWiIgUmVjaXBpZW50PSJodHRwczovLzdlYTItMTAzLTE1My0xMDQtMTYxLm5ncm9rLmlvL3Nzby9vYXV0aC9zYW1sIiAvPg0KICAgICAgICAgPC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgICAgPC9zYW1sMjpTdWJqZWN0Pg0KICAgICAgPHNhbWwyOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDIxLTEyLTA2VDE1OjE4OjA3LjM2MFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0xMi0wNlQxNToyODowNy4zNjBaIj4NCiAgICAgICAgIDxzYW1sMjpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICAgICAgPHNhbWwyOkF1ZGllbmNlPmh0dHBzOi8vc2FtbC5ib3h5aHEuY29tPC9zYW1sMjpBdWRpZW5jZT4NCiAgICAgICAgIDwvc2FtbDI6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgIDwvc2FtbDI6Q29uZGl0aW9ucz4NCiAgICAgIDxzYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICAgICA8c2FtbDI6QXR0cmlidXRlIE5hbWU9InVzZXIuZW1haWwiPg0KICAgICAgICAgICAgPHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOmFueVR5cGUiPmtpcmFuQGRlbW8uY29tPC9zYW1sMjpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgICAgIDwvc2FtbDI6QXR0cmlidXRlPg0KICAgICAgICAgPHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJ1c2VyLmZpcnN0TmFtZSI+DQogICAgICAgICAgICA8c2FtbDI6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6YW55VHlwZSI+S2lyYW48L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgICAgPC9zYW1sMjpBdHRyaWJ1dGU+DQogICAgICAgICA8c2FtbDI6QXR0cmlidXRlIE5hbWU9InVzZXIuaWQiIC8+DQogICAgICAgICA8c2FtbDI6QXR0cmlidXRlIE5hbWU9InVzZXIubGFzdE5hbWUiPg0KICAgICAgICAgICAgPHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOmFueVR5cGUiPks8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgICAgPC9zYW1sMjpBdHRyaWJ1dGU+DQogICAgICAgICA8c2FtbDI6QXR0cmlidXRlIE5hbWU9Im1lbWJlci1vZiIgLz4NCiAgICAgIDwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWwyOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAyMS0xMi0wNlQxNToxNzowNS4wMDBaIiBTZXNzaW9uSW5kZXg9Il8xMWZkN2U5MDdjZmNiMTEwODM3NjI5OGM1Nzc0ZjgyNyI+DQogICAgICAgICA8c2FtbDI6QXV0aG5Db250ZXh0Pg0KICAgICAgICAgICAgPHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgICAgIDwvc2FtbDI6QXV0aG5Db250ZXh0Pg0KICAgICAgPC9zYW1sMjpBdXRoblN0YXRlbWVudD4NCiAgIDwvc2FtbDI6QXNzZXJ0aW9uPg0KPC9zYW1sMnA6UmVzcG9uc2U+
|
package/src/test/oauth.test.js
DELETED
@@ -1,342 +0,0 @@
|
|
1
|
-
const tap = require('tap');
|
2
|
-
const { promises: fs } = require('fs');
|
3
|
-
const path = require('path');
|
4
|
-
const sinon = require('sinon');
|
5
|
-
const crypto = require('crypto');
|
6
|
-
|
7
|
-
const readConfig = require('../read-config');
|
8
|
-
const saml = require('../saml/saml');
|
9
|
-
|
10
|
-
let apiController;
|
11
|
-
let oauthController;
|
12
|
-
|
13
|
-
const code = '1234567890';
|
14
|
-
const token = '24c1550190dd6a5a9bd6fe2a8ff69d593121c7b9';
|
15
|
-
|
16
|
-
const metadataPath = path.join(__dirname, '/data/metadata');
|
17
|
-
|
18
|
-
const options = {
|
19
|
-
externalUrl: 'https://my-cool-app.com',
|
20
|
-
samlAudience: 'https://saml.boxyhq.com',
|
21
|
-
samlPath: '/sso/oauth/saml',
|
22
|
-
db: {
|
23
|
-
engine: 'mem',
|
24
|
-
},
|
25
|
-
};
|
26
|
-
|
27
|
-
const samlConfig = {
|
28
|
-
tenant: 'boxyhq.com',
|
29
|
-
product: 'crm',
|
30
|
-
redirectUrl: '["http://localhost:3000/*"]',
|
31
|
-
defaultRedirectUrl: 'http://localhost:3000/login/saml',
|
32
|
-
rawMetadata: null,
|
33
|
-
};
|
34
|
-
|
35
|
-
const addMetadata = async (metadataPath) => {
|
36
|
-
const configs = await readConfig(metadataPath);
|
37
|
-
|
38
|
-
for (const config of configs) {
|
39
|
-
await apiController.config(config);
|
40
|
-
}
|
41
|
-
};
|
42
|
-
|
43
|
-
tap.before(async () => {
|
44
|
-
const controller = await require('../index.js')(options);
|
45
|
-
|
46
|
-
apiController = controller.apiController;
|
47
|
-
oauthController = controller.oauthController;
|
48
|
-
|
49
|
-
await addMetadata(metadataPath);
|
50
|
-
});
|
51
|
-
|
52
|
-
tap.teardown(async () => {
|
53
|
-
process.exit(0);
|
54
|
-
});
|
55
|
-
|
56
|
-
tap.test('authorize()', async (t) => {
|
57
|
-
t.test('Should throw an error if `redirect_uri` null', async (t) => {
|
58
|
-
const body = {
|
59
|
-
redirect_uri: null,
|
60
|
-
state: 'state',
|
61
|
-
};
|
62
|
-
|
63
|
-
try {
|
64
|
-
await oauthController.authorize(body);
|
65
|
-
t.fail('Expecting JacksonError.');
|
66
|
-
} catch (err) {
|
67
|
-
t.equal(
|
68
|
-
err.message,
|
69
|
-
'Please specify a redirect URL.',
|
70
|
-
'got expected error message'
|
71
|
-
);
|
72
|
-
t.equal(err.statusCode, 400, 'got expected status code');
|
73
|
-
}
|
74
|
-
|
75
|
-
t.end();
|
76
|
-
});
|
77
|
-
|
78
|
-
t.test('Should throw an error if `state` null', async (t) => {
|
79
|
-
const body = {
|
80
|
-
redirect_uri: 'https://example.com/',
|
81
|
-
state: null,
|
82
|
-
};
|
83
|
-
|
84
|
-
try {
|
85
|
-
await oauthController.authorize(body);
|
86
|
-
|
87
|
-
t.fail('Expecting JacksonError.');
|
88
|
-
} catch (err) {
|
89
|
-
t.equal(
|
90
|
-
err.message,
|
91
|
-
'Please specify a state to safeguard against XSRF attacks.',
|
92
|
-
'got expected error message'
|
93
|
-
);
|
94
|
-
t.equal(err.statusCode, 400, 'got expected status code');
|
95
|
-
}
|
96
|
-
|
97
|
-
t.end();
|
98
|
-
});
|
99
|
-
|
100
|
-
t.test('Should throw an error if `client_id` is invalid', async (t) => {
|
101
|
-
const body = {
|
102
|
-
redirect_uri: 'https://example.com/',
|
103
|
-
state: 'state-123',
|
104
|
-
client_id: '27fa9a11875ec3a0',
|
105
|
-
};
|
106
|
-
|
107
|
-
try {
|
108
|
-
await oauthController.authorize(body);
|
109
|
-
|
110
|
-
t.fail('Expecting JacksonError.');
|
111
|
-
} catch (err) {
|
112
|
-
t.equal(
|
113
|
-
err.message,
|
114
|
-
'SAML configuration not found.',
|
115
|
-
'got expected error message'
|
116
|
-
);
|
117
|
-
t.equal(err.statusCode, 403, 'got expected status code');
|
118
|
-
}
|
119
|
-
|
120
|
-
t.end();
|
121
|
-
});
|
122
|
-
|
123
|
-
t.test(
|
124
|
-
'Should throw an error if `redirect_uri` is not allowed',
|
125
|
-
async (t) => {
|
126
|
-
const body = {
|
127
|
-
redirect_uri: 'https://example.com/',
|
128
|
-
state: 'state-123',
|
129
|
-
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
130
|
-
};
|
131
|
-
|
132
|
-
try {
|
133
|
-
await oauthController.authorize(body);
|
134
|
-
|
135
|
-
t.fail('Expecting JacksonError.');
|
136
|
-
} catch (err) {
|
137
|
-
t.equal(
|
138
|
-
err.message,
|
139
|
-
'Redirect URL is not allowed.',
|
140
|
-
'got expected error message'
|
141
|
-
);
|
142
|
-
t.equal(err.statusCode, 403, 'got expected status code');
|
143
|
-
}
|
144
|
-
|
145
|
-
t.end();
|
146
|
-
}
|
147
|
-
);
|
148
|
-
|
149
|
-
t.test('Should return the Idp SSO URL', async (t) => {
|
150
|
-
const body = {
|
151
|
-
redirect_uri: samlConfig.defaultRedirectUrl,
|
152
|
-
state: 'state-123',
|
153
|
-
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
154
|
-
};
|
155
|
-
|
156
|
-
const response = await oauthController.authorize(body);
|
157
|
-
const params = new URLSearchParams(new URL(response.redirect_url).search);
|
158
|
-
|
159
|
-
t.ok('redirect_url' in response, 'got the Idp authorize URL');
|
160
|
-
t.ok(params.has('RelayState'), 'RelayState present in the query string');
|
161
|
-
t.ok(params.has('SAMLRequest'), 'SAMLRequest present in the query string');
|
162
|
-
|
163
|
-
t.end();
|
164
|
-
});
|
165
|
-
|
166
|
-
t.end();
|
167
|
-
});
|
168
|
-
|
169
|
-
tap.test('samlResponse()', async (t) => {
|
170
|
-
const authBody = {
|
171
|
-
redirect_uri: samlConfig.defaultRedirectUrl,
|
172
|
-
state: 'state-123',
|
173
|
-
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
174
|
-
};
|
175
|
-
|
176
|
-
const { redirect_url } = await oauthController.authorize(authBody);
|
177
|
-
|
178
|
-
const relayState = new URLSearchParams(new URL(redirect_url).search).get(
|
179
|
-
'RelayState'
|
180
|
-
);
|
181
|
-
|
182
|
-
const rawResponse = await fs.readFile(
|
183
|
-
path.join(__dirname, '/data/saml_response'),
|
184
|
-
'utf8'
|
185
|
-
);
|
186
|
-
|
187
|
-
t.test('Should throw an error if `RelayState` is missing', async (t) => {
|
188
|
-
const responseBody = {
|
189
|
-
SAMLResponse: rawResponse,
|
190
|
-
};
|
191
|
-
|
192
|
-
try {
|
193
|
-
await oauthController.samlResponse(responseBody);
|
194
|
-
|
195
|
-
t.fail('Expecting JacksonError.');
|
196
|
-
} catch (err) {
|
197
|
-
t.equal(
|
198
|
-
err.message,
|
199
|
-
'IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.',
|
200
|
-
'got expected error message'
|
201
|
-
);
|
202
|
-
|
203
|
-
t.equal(err.statusCode, 403, 'got expected status code');
|
204
|
-
}
|
205
|
-
|
206
|
-
t.end();
|
207
|
-
});
|
208
|
-
|
209
|
-
t.test(
|
210
|
-
'Should return a URL with code and state as query params',
|
211
|
-
async (t) => {
|
212
|
-
const responseBody = {
|
213
|
-
SAMLResponse: rawResponse,
|
214
|
-
RelayState: relayState,
|
215
|
-
};
|
216
|
-
|
217
|
-
const stubValidateAsync = sinon.stub(saml, 'validateAsync').returns({
|
218
|
-
id: 1,
|
219
|
-
email: 'john@example.com',
|
220
|
-
firstName: 'John',
|
221
|
-
lastName: 'Doe',
|
222
|
-
});
|
223
|
-
|
224
|
-
const stubRandomBytes = sinon.stub(crypto, 'randomBytes').returns(code);
|
225
|
-
|
226
|
-
const response = await oauthController.samlResponse(responseBody);
|
227
|
-
|
228
|
-
const params = new URLSearchParams(new URL(response.redirect_url).search);
|
229
|
-
|
230
|
-
t.ok(stubValidateAsync.calledOnce, 'randomBytes called once');
|
231
|
-
t.ok(stubRandomBytes.calledOnce, 'validateAsync called once');
|
232
|
-
t.ok('redirect_url' in response, 'response contains redirect_url');
|
233
|
-
t.ok(params.has('code'), 'query string includes code');
|
234
|
-
t.ok(params.has('state'), 'query string includes state');
|
235
|
-
t.match(params.get('state'), authBody.state, 'state value is valid');
|
236
|
-
|
237
|
-
stubRandomBytes.restore();
|
238
|
-
stubValidateAsync.restore();
|
239
|
-
|
240
|
-
t.end();
|
241
|
-
}
|
242
|
-
);
|
243
|
-
|
244
|
-
t.end();
|
245
|
-
});
|
246
|
-
|
247
|
-
tap.test('token()', (t) => {
|
248
|
-
t.test(
|
249
|
-
'Should throw an error if `grant_type` is not `authorization_code`',
|
250
|
-
async (t) => {
|
251
|
-
const body = {
|
252
|
-
grant_type: 'authorization_code_1',
|
253
|
-
};
|
254
|
-
|
255
|
-
try {
|
256
|
-
await oauthController.token(body);
|
257
|
-
|
258
|
-
t.fail('Expecting JacksonError.');
|
259
|
-
} catch (err) {
|
260
|
-
t.equal(
|
261
|
-
err.message,
|
262
|
-
'Unsupported grant_type',
|
263
|
-
'got expected error message'
|
264
|
-
);
|
265
|
-
t.equal(err.statusCode, 400, 'got expected status code');
|
266
|
-
}
|
267
|
-
|
268
|
-
t.end();
|
269
|
-
}
|
270
|
-
);
|
271
|
-
|
272
|
-
t.test('Should throw an error if `code` is missing', async (t) => {
|
273
|
-
const body = {
|
274
|
-
grant_type: 'authorization_code',
|
275
|
-
};
|
276
|
-
|
277
|
-
try {
|
278
|
-
await oauthController.token(body);
|
279
|
-
|
280
|
-
t.fail('Expecting JacksonError.');
|
281
|
-
} catch (err) {
|
282
|
-
t.equal(err.message, 'Please specify code', 'got expected error message');
|
283
|
-
t.equal(err.statusCode, 400, 'got expected status code');
|
284
|
-
}
|
285
|
-
|
286
|
-
t.end();
|
287
|
-
});
|
288
|
-
|
289
|
-
t.test('Should throw an error if `code` is invalid', async (t) => {
|
290
|
-
const body = {
|
291
|
-
grant_type: 'authorization_code',
|
292
|
-
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
293
|
-
client_secret: 'some-secret',
|
294
|
-
redirect_uri: null,
|
295
|
-
code: 'invalid-code',
|
296
|
-
};
|
297
|
-
|
298
|
-
try {
|
299
|
-
await oauthController.token(body);
|
300
|
-
|
301
|
-
t.fail('Expecting JacksonError.');
|
302
|
-
} catch (err) {
|
303
|
-
t.equal(err.message, 'Invalid code', 'got expected error message');
|
304
|
-
t.equal(err.statusCode, 403, 'got expected status code');
|
305
|
-
}
|
306
|
-
|
307
|
-
t.end();
|
308
|
-
});
|
309
|
-
|
310
|
-
t.test('Should return the `access_token` for a valid request', async (t) => {
|
311
|
-
const body = {
|
312
|
-
grant_type: 'authorization_code',
|
313
|
-
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
314
|
-
client_secret: 'some-secret',
|
315
|
-
redirect_uri: null,
|
316
|
-
code: code,
|
317
|
-
};
|
318
|
-
|
319
|
-
const stubRandomBytes = sinon.stub(crypto, 'randomBytes').returns(token);
|
320
|
-
|
321
|
-
const response = await oauthController.token(body);
|
322
|
-
|
323
|
-
t.ok(stubRandomBytes.calledOnce, 'randomBytes called once');
|
324
|
-
t.ok('access_token' in response, 'includes access_token');
|
325
|
-
t.ok('token_type' in response, 'includes token_type');
|
326
|
-
t.ok('expires_in' in response, 'includes expires_in');
|
327
|
-
t.match(response.access_token, token);
|
328
|
-
t.match(response.token_type, 'bearer');
|
329
|
-
t.match(response.expires_in, 300);
|
330
|
-
|
331
|
-
stubRandomBytes.reset();
|
332
|
-
|
333
|
-
t.end();
|
334
|
-
});
|
335
|
-
|
336
|
-
// TODO
|
337
|
-
t.test('Handle invalid client_id', async (t) => {
|
338
|
-
t.end();
|
339
|
-
});
|
340
|
-
|
341
|
-
t.end();
|
342
|
-
});
|