@boxyhq/saml-jackson 0.2.3-beta.223 → 0.2.3-beta.227
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 +3 -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/model/JacksonStore.ts +3 -3
- package/src/db/sql/sql.ts +12 -15
- package/src/jackson.ts +4 -4
- package/src/test/api.test.ts +1 -2
- package/src/test/oauth.test.ts +36 -27
- package/src/typings.ts +3 -3
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.227",
|
4
4
|
"license": "Apache 2.0",
|
5
5
|
"description": "SAML 2.0 service",
|
6
6
|
"main": "dist/index.js",
|
@@ -19,8 +19,8 @@
|
|
19
19
|
"build": "tsc -p tsconfig.build.json",
|
20
20
|
"start": "cross-env IDP_ENABLED=true node dist/jackson.js",
|
21
21
|
"dev": "cross-env IDP_ENABLED=true nodemon --config nodemon.json src/jackson.ts",
|
22
|
-
"mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson
|
23
|
-
"sql": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=sql DB_TYPE=postgres DB_URL=postgres://postgres:postgres@localhost:5432/jackson
|
22
|
+
"mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson nodemon --config nodemon.json src/jackson.ts",
|
23
|
+
"sql": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=sql DB_TYPE=postgres DB_URL=postgres://postgres:postgres@localhost:5432/jackson nodemon --config nodemon.json src/jackson.ts",
|
24
24
|
"pre-loaded": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mem PRE_LOADED_CONFIG='./_config' nodemon --config nodemon.json src/jackson.ts",
|
25
25
|
"pre-loaded-db": "cross-env JACKSON_API_KEYS=secret PRE_LOADED_CONFIG='./_config' nodemon --config nodemon.json src/jackson.ts",
|
26
26
|
"test": "tap --ts --timeout=100 src/**/*.test.ts",
|
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/jackson.ts
CHANGED
@@ -1,12 +1,13 @@
|
|
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
7
|
import jackson from './index';
|
7
8
|
|
8
|
-
let apiController;
|
9
|
-
let oauthController;
|
9
|
+
let apiController: ISAMLConfig;
|
10
|
+
let oauthController: IOAuthController;
|
10
11
|
|
11
12
|
const oauthPath = '/oauth';
|
12
13
|
const apiPath = '/api/v1/saml';
|
@@ -62,7 +63,7 @@ app.get(oauthPath + '/userinfo', async (req, res) => {
|
|
62
63
|
}
|
63
64
|
|
64
65
|
if (!token) {
|
65
|
-
res.status(401).json({ message: 'Unauthorized' });
|
66
|
+
return res.status(401).json({ message: 'Unauthorized' });
|
66
67
|
}
|
67
68
|
|
68
69
|
const profile = await oauthController.userInfo(token);
|
@@ -80,7 +81,6 @@ const server = app.listen(env.hostPort, async () => {
|
|
80
81
|
`🚀 The path of the righteous server: http://${env.hostUrl}:${env.hostPort}`
|
81
82
|
);
|
82
83
|
|
83
|
-
// TODO: Fix it
|
84
84
|
// @ts-ignore
|
85
85
|
const ctrlrModule = await jackson(env);
|
86
86
|
|
package/src/test/api.test.ts
CHANGED
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
|
|