@boxyhq/saml-jackson 0.2.3-beta.225 → 0.2.3-beta.230
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintrc.js +16 -11
- package/package.json +6 -3
- package/src/db/mem.ts +2 -2
- package/src/db/mongo.ts +3 -3
- package/src/db/redis.ts +2 -2
- package/src/db/sql/entity/JacksonIndex.ts +0 -1
- package/src/db/sql/model/JacksonStore.ts +3 -3
- package/src/db/sql/sql.ts +12 -15
- package/src/index.ts +8 -3
- package/src/jackson.ts +8 -5
- package/src/test/oauth.test.ts +36 -27
- package/src/typings.ts +3 -3
package/.eslintrc.js
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module.exports = {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
2
|
+
env: {
|
3
|
+
es2021: true,
|
4
|
+
node: true,
|
5
|
+
},
|
6
|
+
parserOptions: {
|
7
|
+
ecmaVersion: 13,
|
8
|
+
sourceType: 'module',
|
9
|
+
},
|
10
|
+
root: true,
|
11
|
+
parser: '@typescript-eslint/parser',
|
12
|
+
plugins: ['@typescript-eslint'],
|
13
|
+
extends: [
|
14
|
+
'eslint:recommended',
|
15
|
+
'plugin:@typescript-eslint/recommended',
|
16
|
+
'prettier',
|
17
|
+
],
|
13
18
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.2.3-beta.
|
3
|
+
"version": "0.2.3-beta.230",
|
4
4
|
"license": "Apache 2.0",
|
5
5
|
"description": "SAML 2.0 service",
|
6
6
|
"main": "dist/index.js",
|
@@ -57,8 +57,11 @@
|
|
57
57
|
"@types/redis": "4.0.11",
|
58
58
|
"@types/sinon": "10.0.6",
|
59
59
|
"@types/tap": "15.0.5",
|
60
|
+
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
61
|
+
"@typescript-eslint/parser": "^5.8.1",
|
60
62
|
"cross-env": "7.0.3",
|
61
|
-
"eslint": "8.5.0",
|
63
|
+
"eslint": "^8.5.0",
|
64
|
+
"eslint-config-prettier": "^8.3.0",
|
62
65
|
"husky": "7.0.4",
|
63
66
|
"lint-staged": "12.1.4",
|
64
67
|
"nodemon": "2.0.15",
|
@@ -66,7 +69,7 @@
|
|
66
69
|
"sinon": "12.0.1",
|
67
70
|
"tap": "15.1.5",
|
68
71
|
"ts-node": "10.4.0",
|
69
|
-
"typescript": "4.5.4"
|
72
|
+
"typescript": "^4.5.4"
|
70
73
|
},
|
71
74
|
"lint-staged": {
|
72
75
|
"*.{js,ts}": "eslint --cache --fix",
|
package/src/db/mem.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
// This is an in-memory implementation to be used with testing and prototyping only
|
2
2
|
|
3
|
-
import { DatabaseDriver, DatabaseOption, Index } from 'saml-jackson';
|
3
|
+
import { DatabaseDriver, DatabaseOption, Index, Encrypted } from 'saml-jackson';
|
4
4
|
import * as dbutils from './utils';
|
5
5
|
|
6
6
|
class Mem implements DatabaseDriver {
|
@@ -65,7 +65,7 @@ class Mem implements DatabaseDriver {
|
|
65
65
|
async put(
|
66
66
|
namespace: string,
|
67
67
|
key: string,
|
68
|
-
val:
|
68
|
+
val: Encrypted,
|
69
69
|
ttl: number = 0,
|
70
70
|
...indexes: any[]
|
71
71
|
): Promise<any> {
|
package/src/db/mongo.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import { MongoClient } from 'mongodb';
|
2
|
-
import { DatabaseDriver, DatabaseOption, Index } from 'saml-jackson';
|
2
|
+
import { DatabaseDriver, DatabaseOption, Encrypted, Index } from 'saml-jackson';
|
3
3
|
import * as dbutils from './utils';
|
4
4
|
|
5
5
|
type Document = {
|
6
|
-
value:
|
6
|
+
value: Encrypted;
|
7
7
|
expiresAt: Date;
|
8
8
|
indexes: string[];
|
9
9
|
};
|
@@ -64,7 +64,7 @@ class Mongo implements DatabaseDriver {
|
|
64
64
|
async put(
|
65
65
|
namespace: string,
|
66
66
|
key: string,
|
67
|
-
val:
|
67
|
+
val: Encrypted,
|
68
68
|
ttl: number = 0,
|
69
69
|
...indexes: any[]
|
70
70
|
): Promise<void> {
|
package/src/db/redis.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import * as redis from 'redis';
|
2
|
-
import { DatabaseDriver, DatabaseOption, Index } from 'saml-jackson';
|
2
|
+
import { DatabaseDriver, DatabaseOption, Encrypted, Index } from 'saml-jackson';
|
3
3
|
import * as dbutils from './utils';
|
4
4
|
|
5
5
|
class Redis implements DatabaseDriver {
|
@@ -54,7 +54,7 @@ class Redis implements DatabaseDriver {
|
|
54
54
|
async put(
|
55
55
|
namespace: string,
|
56
56
|
key: string,
|
57
|
-
val:
|
57
|
+
val: Encrypted,
|
58
58
|
ttl: number = 0,
|
59
59
|
...indexes: any[]
|
60
60
|
): Promise<void> {
|
package/src/db/sql/sql.ts
CHANGED
@@ -2,23 +2,24 @@
|
|
2
2
|
|
3
3
|
require('reflect-metadata');
|
4
4
|
|
5
|
-
import { DatabaseDriver, DatabaseOption, Index } from 'saml-jackson';
|
5
|
+
import { DatabaseDriver, DatabaseOption, Index, Encrypted } from 'saml-jackson';
|
6
6
|
import { Connection, createConnection } from 'typeorm';
|
7
7
|
import * as dbutils from '../utils';
|
8
|
+
import JacksonIndexEntity from './entity/JacksonIndex';
|
9
|
+
import JacksonStoreEntity from './entity/JacksonStore';
|
10
|
+
import {
|
11
|
+
JacksonTTL,
|
12
|
+
JacksonTTL as JacksonTTLEntity,
|
13
|
+
} from './entity/JacksonTTL';
|
8
14
|
import { JacksonIndex } from './model/JacksonIndex';
|
9
15
|
import { JacksonStore } from './model/JacksonStore';
|
10
|
-
import { JacksonTTL } from './entity/JacksonTTL';
|
11
|
-
|
12
|
-
import JacksonStoreEntity from './entity/JacksonStore';
|
13
|
-
import JacksonIndexEntity from './entity/JacksonIndex';
|
14
|
-
import { JacksonTTL as JacksonTTLEntity } from './entity/JacksonTTL';
|
15
16
|
|
16
17
|
class Sql implements DatabaseDriver {
|
17
18
|
private options: DatabaseOption;
|
18
19
|
private connection!: Connection;
|
19
|
-
private storeRepository;
|
20
|
-
private indexRepository;
|
21
|
-
private ttlRepository;
|
20
|
+
private storeRepository;
|
21
|
+
private indexRepository;
|
22
|
+
private ttlRepository;
|
22
23
|
private ttlCleanup;
|
23
24
|
private timerId;
|
24
25
|
|
@@ -29,8 +30,6 @@ class Sql implements DatabaseDriver {
|
|
29
30
|
async init(): Promise<Sql> {
|
30
31
|
while (true) {
|
31
32
|
try {
|
32
|
-
// TODO: Fix it
|
33
|
-
// @ts-ignore
|
34
33
|
this.connection = await createConnection({
|
35
34
|
name: this.options.type + Math.floor(Math.random() * 100000),
|
36
35
|
type: this.options.type,
|
@@ -116,11 +115,10 @@ class Sql implements DatabaseDriver {
|
|
116
115
|
key: dbutils.keyForIndex(namespace, idx),
|
117
116
|
});
|
118
117
|
|
119
|
-
const ret:
|
118
|
+
const ret: Encrypted[] = [];
|
120
119
|
|
121
120
|
if (res) {
|
122
121
|
res.forEach((r) => {
|
123
|
-
// @ts-ignore
|
124
122
|
ret.push({
|
125
123
|
value: r.store.value,
|
126
124
|
iv: r.store.iv,
|
@@ -135,14 +133,13 @@ class Sql implements DatabaseDriver {
|
|
135
133
|
async put(
|
136
134
|
namespace: string,
|
137
135
|
key: string,
|
138
|
-
val:
|
136
|
+
val: Encrypted,
|
139
137
|
ttl: number = 0,
|
140
138
|
...indexes: any[]
|
141
139
|
): Promise<void> {
|
142
140
|
await this.connection.transaction(async (transactionalEntityManager) => {
|
143
141
|
const dbKey = dbutils.key(namespace, key);
|
144
142
|
|
145
|
-
// @ts-ignore
|
146
143
|
const store = new JacksonStore(dbKey, val.value, val.iv, val.tag);
|
147
144
|
|
148
145
|
await transactionalEntityManager.save(store);
|
package/src/index.ts
CHANGED
@@ -32,9 +32,12 @@ const defaultOpts = (opts: JacksonOption): JacksonOption => {
|
|
32
32
|
return newOpts;
|
33
33
|
};
|
34
34
|
|
35
|
-
|
35
|
+
const controllers = async (
|
36
36
|
opts: JacksonOption
|
37
|
-
): Promise<{
|
37
|
+
): Promise<{
|
38
|
+
apiController: SAMLConfig;
|
39
|
+
oauthController: OAuthController;
|
40
|
+
}> => {
|
38
41
|
opts = defaultOpts(opts);
|
39
42
|
|
40
43
|
const db = await DB.new(opts.db);
|
@@ -76,4 +79,6 @@ export default async function controllers(
|
|
76
79
|
apiController,
|
77
80
|
oauthController,
|
78
81
|
};
|
79
|
-
}
|
82
|
+
};
|
83
|
+
|
84
|
+
export default controllers;
|
package/src/jackson.ts
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
import cors from 'cors';
|
2
2
|
import express from 'express';
|
3
|
+
import { IOAuthController, ISAMLConfig } from 'saml-jackson';
|
3
4
|
import { JacksonError } from './controller/error';
|
4
5
|
import { extractAuthToken } from './controller/utils';
|
5
6
|
import env from './env';
|
6
|
-
import jackson from './index';
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
//import jackson from './index';
|
9
|
+
|
10
|
+
const jackson = require('./index');
|
11
|
+
|
12
|
+
let apiController: ISAMLConfig;
|
13
|
+
let oauthController: IOAuthController;
|
10
14
|
|
11
15
|
const oauthPath = '/oauth';
|
12
16
|
const apiPath = '/api/v1/saml';
|
@@ -62,7 +66,7 @@ app.get(oauthPath + '/userinfo', async (req, res) => {
|
|
62
66
|
}
|
63
67
|
|
64
68
|
if (!token) {
|
65
|
-
res.status(401).json({ message: 'Unauthorized' });
|
69
|
+
return res.status(401).json({ message: 'Unauthorized' });
|
66
70
|
}
|
67
71
|
|
68
72
|
const profile = await oauthController.userInfo(token);
|
@@ -80,7 +84,6 @@ const server = app.listen(env.hostPort, async () => {
|
|
80
84
|
`🚀 The path of the righteous server: http://${env.hostUrl}:${env.hostPort}`
|
81
85
|
);
|
82
86
|
|
83
|
-
// TODO: Fix it
|
84
87
|
// @ts-ignore
|
85
88
|
const ctrlrModule = await jackson(env);
|
86
89
|
|
package/src/test/oauth.test.ts
CHANGED
@@ -1,30 +1,36 @@
|
|
1
1
|
import crypto from 'crypto';
|
2
2
|
import { promises as fs } from 'fs';
|
3
3
|
import path from 'path';
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
IOAuthController,
|
6
|
+
ISAMLConfig,
|
7
|
+
JacksonOption,
|
8
|
+
OAuthReqBody,
|
9
|
+
OAuthTokenReq,
|
10
|
+
SAMLResponsePayload,
|
11
|
+
} from 'saml-jackson';
|
5
12
|
import sinon from 'sinon';
|
6
13
|
import tap from 'tap';
|
7
14
|
import { JacksonError } from '../controller/error';
|
8
15
|
import readConfig from '../read-config';
|
9
16
|
import saml from '../saml/saml';
|
10
17
|
|
11
|
-
|
12
|
-
let
|
13
|
-
let oauthController;
|
18
|
+
let apiController: ISAMLConfig;
|
19
|
+
let oauthController: IOAuthController;
|
14
20
|
|
15
21
|
const code = '1234567890';
|
16
22
|
const token = '24c1550190dd6a5a9bd6fe2a8ff69d593121c7b9';
|
17
23
|
|
18
24
|
const metadataPath = path.join(__dirname, '/data/metadata');
|
19
25
|
|
20
|
-
const options = {
|
26
|
+
const options = <JacksonOption>{
|
21
27
|
externalUrl: 'https://my-cool-app.com',
|
22
28
|
samlAudience: 'https://saml.boxyhq.com',
|
23
29
|
samlPath: '/sso/oauth/saml',
|
24
30
|
db: {
|
25
31
|
engine: 'mem',
|
26
32
|
},
|
27
|
-
}
|
33
|
+
};
|
28
34
|
|
29
35
|
const samlConfig = {
|
30
36
|
tenant: 'boxyhq.com',
|
@@ -57,13 +63,13 @@ tap.teardown(async () => {
|
|
57
63
|
|
58
64
|
tap.test('authorize()', async (t) => {
|
59
65
|
t.test('Should throw an error if `redirect_uri` null', async (t) => {
|
60
|
-
const body = {
|
61
|
-
redirect_uri:
|
66
|
+
const body: Partial<OAuthReqBody> = {
|
67
|
+
redirect_uri: undefined,
|
62
68
|
state: 'state',
|
63
69
|
};
|
64
70
|
|
65
71
|
try {
|
66
|
-
await oauthController.authorize(body);
|
72
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
67
73
|
t.fail('Expecting JacksonError.');
|
68
74
|
} catch (err) {
|
69
75
|
const { message, statusCode } = err as JacksonError;
|
@@ -79,13 +85,13 @@ tap.test('authorize()', async (t) => {
|
|
79
85
|
});
|
80
86
|
|
81
87
|
t.test('Should throw an error if `state` null', async (t) => {
|
82
|
-
const body = {
|
88
|
+
const body: Partial<OAuthReqBody> = {
|
83
89
|
redirect_uri: 'https://example.com/',
|
84
|
-
state:
|
90
|
+
state: undefined,
|
85
91
|
};
|
86
92
|
|
87
93
|
try {
|
88
|
-
await oauthController.authorize(body);
|
94
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
89
95
|
|
90
96
|
t.fail('Expecting JacksonError.');
|
91
97
|
} catch (err) {
|
@@ -109,7 +115,7 @@ tap.test('authorize()', async (t) => {
|
|
109
115
|
};
|
110
116
|
|
111
117
|
try {
|
112
|
-
await oauthController.authorize(body);
|
118
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
113
119
|
|
114
120
|
t.fail('Expecting JacksonError.');
|
115
121
|
} catch (err) {
|
@@ -135,7 +141,7 @@ tap.test('authorize()', async (t) => {
|
|
135
141
|
};
|
136
142
|
|
137
143
|
try {
|
138
|
-
await oauthController.authorize(body);
|
144
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
139
145
|
|
140
146
|
t.fail('Expecting JacksonError.');
|
141
147
|
} catch (err) {
|
@@ -159,7 +165,7 @@ tap.test('authorize()', async (t) => {
|
|
159
165
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
160
166
|
};
|
161
167
|
|
162
|
-
const response = await oauthController.authorize(body);
|
168
|
+
const response = await oauthController.authorize(<OAuthReqBody>body);
|
163
169
|
const params = new URLSearchParams(new URL(response.redirect_url).search);
|
164
170
|
|
165
171
|
t.ok('redirect_url' in response, 'got the Idp authorize URL');
|
@@ -179,7 +185,9 @@ tap.test('samlResponse()', async (t) => {
|
|
179
185
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
180
186
|
};
|
181
187
|
|
182
|
-
const { redirect_url } = await oauthController.authorize(
|
188
|
+
const { redirect_url } = await oauthController.authorize(
|
189
|
+
<OAuthReqBody>authBody
|
190
|
+
);
|
183
191
|
|
184
192
|
const relayState = new URLSearchParams(new URL(redirect_url).search).get(
|
185
193
|
'RelayState'
|
@@ -191,12 +199,12 @@ tap.test('samlResponse()', async (t) => {
|
|
191
199
|
);
|
192
200
|
|
193
201
|
t.test('Should throw an error if `RelayState` is missing', async (t) => {
|
194
|
-
const responseBody = {
|
202
|
+
const responseBody: Partial<SAMLResponsePayload> = {
|
195
203
|
SAMLResponse: rawResponse,
|
196
204
|
};
|
197
205
|
|
198
206
|
try {
|
199
|
-
await oauthController.samlResponse(responseBody);
|
207
|
+
await oauthController.samlResponse(<SAMLResponsePayload>responseBody);
|
200
208
|
|
201
209
|
t.fail('Expecting JacksonError.');
|
202
210
|
} catch (err) {
|
@@ -224,10 +232,13 @@ tap.test('samlResponse()', async (t) => {
|
|
224
232
|
const stubValidateAsync = sinon
|
225
233
|
.stub(saml, 'validateAsync')
|
226
234
|
.resolves({ audience: '', claims: {}, issuer: '', sessionIndex: '' });
|
235
|
+
|
227
236
|
//@ts-ignore
|
228
237
|
const stubRandomBytes = sinon.stub(crypto, 'randomBytes').returns(code);
|
229
238
|
|
230
|
-
const response = await oauthController.samlResponse(
|
239
|
+
const response = await oauthController.samlResponse(
|
240
|
+
<SAMLResponsePayload>responseBody
|
241
|
+
);
|
231
242
|
|
232
243
|
const params = new URLSearchParams(new URL(response.redirect_url).search);
|
233
244
|
|
@@ -257,7 +268,7 @@ tap.test('token()', (t) => {
|
|
257
268
|
};
|
258
269
|
|
259
270
|
try {
|
260
|
-
await oauthController.token(body);
|
271
|
+
await oauthController.token(<OAuthTokenReq>body);
|
261
272
|
|
262
273
|
t.fail('Expecting JacksonError.');
|
263
274
|
} catch (err) {
|
@@ -280,7 +291,7 @@ tap.test('token()', (t) => {
|
|
280
291
|
};
|
281
292
|
|
282
293
|
try {
|
283
|
-
await oauthController.token(body);
|
294
|
+
await oauthController.token(<OAuthTokenReq>body);
|
284
295
|
|
285
296
|
t.fail('Expecting JacksonError.');
|
286
297
|
} catch (err) {
|
@@ -293,16 +304,15 @@ tap.test('token()', (t) => {
|
|
293
304
|
});
|
294
305
|
|
295
306
|
t.test('Should throw an error if `code` is invalid', async (t) => {
|
296
|
-
const body = {
|
307
|
+
const body: Partial<OAuthTokenReq> = {
|
297
308
|
grant_type: 'authorization_code',
|
298
309
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
299
310
|
client_secret: 'some-secret',
|
300
|
-
redirect_uri: null,
|
301
311
|
code: 'invalid-code',
|
302
312
|
};
|
303
313
|
|
304
314
|
try {
|
305
|
-
await oauthController.token(body);
|
315
|
+
await oauthController.token(<OAuthTokenReq>body);
|
306
316
|
|
307
317
|
t.fail('Expecting JacksonError.');
|
308
318
|
} catch (err) {
|
@@ -315,11 +325,10 @@ tap.test('token()', (t) => {
|
|
315
325
|
});
|
316
326
|
|
317
327
|
t.test('Should return the `access_token` for a valid request', async (t) => {
|
318
|
-
const body = {
|
328
|
+
const body: Partial<OAuthTokenReq> = {
|
319
329
|
grant_type: 'authorization_code',
|
320
330
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
321
331
|
client_secret: 'some-secret',
|
322
|
-
redirect_uri: null,
|
323
332
|
code: code,
|
324
333
|
};
|
325
334
|
|
@@ -329,7 +338,7 @@ tap.test('token()', (t) => {
|
|
329
338
|
//@ts-ignore
|
330
339
|
.returns(token);
|
331
340
|
|
332
|
-
const response = await oauthController.token(body);
|
341
|
+
const response = await oauthController.token(<OAuthTokenReq>body);
|
333
342
|
|
334
343
|
t.ok(stubRandomBytes.calledOnce, 'randomBytes called once');
|
335
344
|
t.ok('access_token' in response, 'includes access_token');
|
package/src/typings.ts
CHANGED
@@ -51,7 +51,7 @@ declare module 'saml-jackson' {
|
|
51
51
|
authorize(body: OAuthReqBody): Promise<{ redirect_url: string }>;
|
52
52
|
samlResponse(body: SAMLResponsePayload): Promise<{ redirect_url: string }>;
|
53
53
|
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
54
|
-
userInfo(
|
54
|
+
userInfo(token: string): Promise<Profile>;
|
55
55
|
}
|
56
56
|
|
57
57
|
export interface OAuthReqBody {
|
@@ -118,8 +118,8 @@ declare module 'saml-jackson' {
|
|
118
118
|
}
|
119
119
|
|
120
120
|
export interface Encrypted {
|
121
|
-
iv
|
122
|
-
tag
|
121
|
+
iv?: string;
|
122
|
+
tag?: string;
|
123
123
|
value: string;
|
124
124
|
}
|
125
125
|
|