@boxyhq/saml-jackson 0.4.0 → 0.4.1

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.
@@ -0,0 +1,8 @@
1
+ import { IAdminController, Storable, OAuth } from '../typings';
2
+ export declare class AdminController implements IAdminController {
3
+ configStore: Storable;
4
+ constructor({ configStore }: {
5
+ configStore: any;
6
+ });
7
+ getAllConfig(): Promise<Partial<OAuth>[]>;
8
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AdminController = void 0;
13
+ class AdminController {
14
+ constructor({ configStore }) {
15
+ this.configStore = configStore;
16
+ }
17
+ getAllConfig() {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ const configList = (yield this.configStore.getAll());
20
+ if (!configList || !configList.length) {
21
+ return [];
22
+ }
23
+ return configList;
24
+ });
25
+ }
26
+ }
27
+ exports.AdminController = AdminController;
@@ -18,35 +18,42 @@ export declare class APIController implements IAPIController {
18
18
  * consumes:
19
19
  * - application/x-www-form-urlencoded
20
20
  * parameters:
21
+ * - name: name
22
+ * description: Name/identifier for the config
23
+ * type: string
24
+ * in: formData
25
+ * - name: description
26
+ * description: A short description for the config not more than 100 characters
27
+ * type: string
28
+ * in: formData
21
29
  * - name: encodedRawMetadata
22
30
  * description: Base64 encoding of the XML metadata
23
31
  * in: formData
24
- * required: true
32
+ * type: string
33
+ * - name: rawMetadata
34
+ * description: Raw XML metadata
35
+ * in: formData
25
36
  * type: string
26
37
  * - name: defaultRedirectUrl
27
38
  * description: The redirect URL to use in the IdP login flow
28
39
  * in: formData
29
40
  * required: true
30
41
  * type: string
31
- * example: http://localhost:3366/login/saml
32
42
  * - name: redirectUrl
33
43
  * description: JSON encoded array containing a list of allowed redirect URLs
34
44
  * in: formData
35
45
  * required: true
36
46
  * type: string
37
- * example: '["http://localhost:3366/*"]'
38
47
  * - name: tenant
39
48
  * description: Tenant
40
49
  * in: formData
41
50
  * required: true
42
51
  * type: string
43
- * example: boxyhq.com
44
52
  * - name: product
45
53
  * description: Product
46
54
  * in: formData
47
55
  * required: true
48
56
  * type: string
49
- * example: demo
50
57
  * responses:
51
58
  * 200:
52
59
  * description: Success
@@ -63,10 +70,79 @@ export declare class APIController implements IAPIController {
63
70
  * client_id: 8958e13053832b5af58fdf2ee83f35f5d013dc74
64
71
  * client_secret: 13f01f4df5b01770c616e682d14d3ba23f20948cfa89b1d7
65
72
  * type: accounts.google.com
73
+ * 400:
74
+ * description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters
66
75
  * 401:
67
76
  * description: Unauthorized
68
77
  */
69
78
  config(body: IdPConfig): Promise<OAuth>;
79
+ /**
80
+ * @swagger
81
+ *
82
+ * /api/v1/saml/config:
83
+ * patch:
84
+ * summary: Update SAML configuration
85
+ * operationId: update-saml-config
86
+ * tags: [SAML Config]
87
+ * consumes:
88
+ * - application/json
89
+ * - application/x-www-form-urlencoded
90
+ * parameters:
91
+ * - name: clientID
92
+ * description: Client ID for the config
93
+ * type: string
94
+ * in: formData
95
+ * required: true
96
+ * - name: clientSecret
97
+ * description: Client Secret for the config
98
+ * type: string
99
+ * in: formData
100
+ * required: true
101
+ * - name: name
102
+ * description: Name/identifier for the config
103
+ * type: string
104
+ * in: formData
105
+ * - name: description
106
+ * description: A short description for the config not more than 100 characters
107
+ * type: string
108
+ * in: formData
109
+ * - name: encodedRawMetadata
110
+ * description: Base64 encoding of the XML metadata
111
+ * in: formData
112
+ * type: string
113
+ * - name: rawMetadata
114
+ * description: Raw XML metadata
115
+ * in: formData
116
+ * type: string
117
+ * - name: defaultRedirectUrl
118
+ * description: The redirect URL to use in the IdP login flow
119
+ * in: formData
120
+ * required: true
121
+ * type: string
122
+ * - name: redirectUrl
123
+ * description: JSON encoded array containing a list of allowed redirect URLs
124
+ * in: formData
125
+ * required: true
126
+ * type: string
127
+ * - name: tenant
128
+ * description: Tenant
129
+ * in: formData
130
+ * required: true
131
+ * type: string
132
+ * - name: product
133
+ * description: Product
134
+ * in: formData
135
+ * required: true
136
+ * type: string
137
+ * responses:
138
+ * 204:
139
+ * description: Success
140
+ * 400:
141
+ * description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters
142
+ * 401:
143
+ * description: Unauthorized
144
+ */
145
+ updateConfig(body: any): Promise<void>;
70
146
  /**
71
147
  * @swagger
72
148
  *
@@ -81,12 +157,10 @@ export declare class APIController implements IAPIController {
81
157
  * name: tenant
82
158
  * type: string
83
159
  * description: Tenant
84
- * example: boxyhq.com
85
160
  * - in: query
86
161
  * name: product
87
162
  * type: string
88
163
  * description: Product
89
- * example: demo
90
164
  * - in: query
91
165
  * name: clientID
92
166
  * type: string
@@ -96,11 +170,35 @@ export declare class APIController implements IAPIController {
96
170
  * description: Success
97
171
  * schema:
98
172
  * type: object
99
- * properties:
100
- * provider:
101
- * type: string
102
173
  * example:
103
- * type: accounts.google.com
174
+ * {
175
+ * "config": {
176
+ * "idpMetadata": {
177
+ * "sso": {
178
+ * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
179
+ * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
180
+ * },
181
+ * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
182
+ * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
183
+ * "loginType": "idp",
184
+ * "provider": "okta.com"
185
+ * },
186
+ * "defaultRedirectUrl": "https://hoppscotch.io/",
187
+ * "redirectUrl": ["https://hoppscotch.io/"],
188
+ * "tenant": "hoppscotch.io",
189
+ * "product": "API Engine",
190
+ * "name": "Hoppscotch-SP",
191
+ * "description": "SP for hoppscotch.io",
192
+ * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
193
+ * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
194
+ * "certs": {
195
+ * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
196
+ * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
197
+ * }
198
+ * }
199
+ * }
200
+ * '400':
201
+ * description: Please provide `clientID` or `tenant` and `product`.
104
202
  * '401':
105
203
  * description: Unauthorized
106
204
  */
@@ -108,7 +206,7 @@ export declare class APIController implements IAPIController {
108
206
  clientID: string;
109
207
  tenant: string;
110
208
  product: string;
111
- }): Promise<Partial<OAuth>>;
209
+ }): Promise<any>;
112
210
  /**
113
211
  * @swagger
114
212
  * /api/v1/saml/config:
@@ -134,15 +232,15 @@ export declare class APIController implements IAPIController {
134
232
  * in: formData
135
233
  * type: string
136
234
  * description: Tenant
137
- * example: boxyhq.com
138
235
  * - name: product
139
236
  * in: formData
140
237
  * type: string
141
238
  * description: Product
142
- * example: demo
143
239
  * responses:
144
240
  * '200':
145
241
  * description: Success
242
+ * '400':
243
+ * description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.'
146
244
  * '401':
147
245
  * description: Unauthorized
148
246
  */
@@ -27,6 +27,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
27
27
  step((generator = generator.apply(thisArg, _arguments || [])).next());
28
28
  });
29
29
  };
30
+ var __rest = (this && this.__rest) || function (s, e) {
31
+ var t = {};
32
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
33
+ t[p] = s[p];
34
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
35
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
36
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
37
+ t[p[i]] = s[p[i]];
38
+ }
39
+ return t;
40
+ };
30
41
  var __importDefault = (this && this.__importDefault) || function (mod) {
31
42
  return (mod && mod.__esModule) ? mod : { "default": mod };
32
43
  };
@@ -44,7 +55,7 @@ class APIController {
44
55
  this.configStore = configStore;
45
56
  }
46
57
  _validateIdPConfig(body) {
47
- const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
58
+ const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, description } = body;
48
59
  if (!rawMetadata && !encodedRawMetadata) {
49
60
  throw new error_1.JacksonError('Please provide rawMetadata or encodedRawMetadata', 400);
50
61
  }
@@ -60,6 +71,9 @@ class APIController {
60
71
  if (!product) {
61
72
  throw new error_1.JacksonError('Please provide product', 400);
62
73
  }
74
+ if (description && description.length > 100) {
75
+ throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
76
+ }
63
77
  }
64
78
  /**
65
79
  * @swagger
@@ -74,35 +88,42 @@ class APIController {
74
88
  * consumes:
75
89
  * - application/x-www-form-urlencoded
76
90
  * parameters:
91
+ * - name: name
92
+ * description: Name/identifier for the config
93
+ * type: string
94
+ * in: formData
95
+ * - name: description
96
+ * description: A short description for the config not more than 100 characters
97
+ * type: string
98
+ * in: formData
77
99
  * - name: encodedRawMetadata
78
100
  * description: Base64 encoding of the XML metadata
79
101
  * in: formData
80
- * required: true
102
+ * type: string
103
+ * - name: rawMetadata
104
+ * description: Raw XML metadata
105
+ * in: formData
81
106
  * type: string
82
107
  * - name: defaultRedirectUrl
83
108
  * description: The redirect URL to use in the IdP login flow
84
109
  * in: formData
85
110
  * required: true
86
111
  * type: string
87
- * example: http://localhost:3366/login/saml
88
112
  * - name: redirectUrl
89
113
  * description: JSON encoded array containing a list of allowed redirect URLs
90
114
  * in: formData
91
115
  * required: true
92
116
  * type: string
93
- * example: '["http://localhost:3366/*"]'
94
117
  * - name: tenant
95
118
  * description: Tenant
96
119
  * in: formData
97
120
  * required: true
98
121
  * type: string
99
- * example: boxyhq.com
100
122
  * - name: product
101
123
  * description: Product
102
124
  * in: formData
103
125
  * required: true
104
126
  * type: string
105
- * example: demo
106
127
  * responses:
107
128
  * 200:
108
129
  * description: Success
@@ -119,12 +140,14 @@ class APIController {
119
140
  * client_id: 8958e13053832b5af58fdf2ee83f35f5d013dc74
120
141
  * client_secret: 13f01f4df5b01770c616e682d14d3ba23f20948cfa89b1d7
121
142
  * type: accounts.google.com
143
+ * 400:
144
+ * description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters
122
145
  * 401:
123
146
  * description: Unauthorized
124
147
  */
125
148
  config(body) {
126
149
  return __awaiter(this, void 0, void 0, function* () {
127
- const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
150
+ const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name, description, } = body;
128
151
  metrics.increment('createConfig');
129
152
  this._validateIdPConfig(body);
130
153
  let metaData = rawMetadata;
@@ -157,6 +180,8 @@ class APIController {
157
180
  redirectUrl: JSON.parse(redirectUrl),
158
181
  tenant,
159
182
  product,
183
+ name,
184
+ description,
160
185
  clientID,
161
186
  clientSecret,
162
187
  certs,
@@ -176,6 +201,123 @@ class APIController {
176
201
  };
177
202
  });
178
203
  }
204
+ /**
205
+ * @swagger
206
+ *
207
+ * /api/v1/saml/config:
208
+ * patch:
209
+ * summary: Update SAML configuration
210
+ * operationId: update-saml-config
211
+ * tags: [SAML Config]
212
+ * consumes:
213
+ * - application/json
214
+ * - application/x-www-form-urlencoded
215
+ * parameters:
216
+ * - name: clientID
217
+ * description: Client ID for the config
218
+ * type: string
219
+ * in: formData
220
+ * required: true
221
+ * - name: clientSecret
222
+ * description: Client Secret for the config
223
+ * type: string
224
+ * in: formData
225
+ * required: true
226
+ * - name: name
227
+ * description: Name/identifier for the config
228
+ * type: string
229
+ * in: formData
230
+ * - name: description
231
+ * description: A short description for the config not more than 100 characters
232
+ * type: string
233
+ * in: formData
234
+ * - name: encodedRawMetadata
235
+ * description: Base64 encoding of the XML metadata
236
+ * in: formData
237
+ * type: string
238
+ * - name: rawMetadata
239
+ * description: Raw XML metadata
240
+ * in: formData
241
+ * type: string
242
+ * - name: defaultRedirectUrl
243
+ * description: The redirect URL to use in the IdP login flow
244
+ * in: formData
245
+ * required: true
246
+ * type: string
247
+ * - name: redirectUrl
248
+ * description: JSON encoded array containing a list of allowed redirect URLs
249
+ * in: formData
250
+ * required: true
251
+ * type: string
252
+ * - name: tenant
253
+ * description: Tenant
254
+ * in: formData
255
+ * required: true
256
+ * type: string
257
+ * - name: product
258
+ * description: Product
259
+ * in: formData
260
+ * required: true
261
+ * type: string
262
+ * responses:
263
+ * 204:
264
+ * description: Success
265
+ * 400:
266
+ * description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters
267
+ * 401:
268
+ * description: Unauthorized
269
+ */
270
+ updateConfig(body) {
271
+ var _a;
272
+ return __awaiter(this, void 0, void 0, function* () {
273
+ const { encodedRawMetadata, // could be empty
274
+ rawMetadata, // could be empty
275
+ defaultRedirectUrl, redirectUrl, name, description } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description"]);
276
+ if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
277
+ throw new error_1.JacksonError('Please provide clientID', 400);
278
+ }
279
+ if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
280
+ throw new error_1.JacksonError('Please provide clientSecret', 400);
281
+ }
282
+ if (description && description.length > 100) {
283
+ throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
284
+ }
285
+ const _currentConfig = (_a = (yield this.getConfig(clientInfo))) === null || _a === void 0 ? void 0 : _a.config;
286
+ if (_currentConfig.clientSecret !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
287
+ throw new error_1.JacksonError('clientSecret mismatch', 400);
288
+ }
289
+ let metaData = rawMetadata;
290
+ if (encodedRawMetadata) {
291
+ metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
292
+ }
293
+ let newMetadata;
294
+ if (metaData) {
295
+ newMetadata = yield saml_1.default.parseMetadataAsync(metaData);
296
+ // extract provider
297
+ let providerName = extractHostName(newMetadata.entityID);
298
+ if (!providerName) {
299
+ providerName = extractHostName(newMetadata.sso.redirectUrl || newMetadata.sso.postUrl);
300
+ }
301
+ newMetadata.provider = providerName ? providerName : 'Unknown';
302
+ }
303
+ if (newMetadata) {
304
+ // check if clientID matches with new metadata payload
305
+ const clientID = dbutils.keyDigest(dbutils.keyFromParts(clientInfo.tenant, clientInfo.product, newMetadata.entityID));
306
+ if (clientID !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
307
+ throw new error_1.JacksonError('Tenant/Product config mismatch with IdP metadata', 400);
308
+ }
309
+ }
310
+ yield this.configStore.put(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID, Object.assign(Object.assign({}, _currentConfig), { name: name ? name : _currentConfig.name, description: description ? description : _currentConfig.description, idpMetadata: newMetadata ? newMetadata : _currentConfig.idpMetadata, defaultRedirectUrl: defaultRedirectUrl ? defaultRedirectUrl : _currentConfig.defaultRedirectUrl, redirectUrl: redirectUrl ? JSON.parse(redirectUrl) : _currentConfig.redirectUrl }), {
311
+ // secondary index on entityID
312
+ name: utils_1.IndexNames.EntityID,
313
+ value: _currentConfig.idpMetadata.entityID,
314
+ }, {
315
+ // secondary index on tenant + product
316
+ name: utils_1.IndexNames.TenantProduct,
317
+ value: dbutils.keyFromParts(_currentConfig.tenant, _currentConfig.product),
318
+ });
319
+ });
320
+ }
179
321
  /**
180
322
  * @swagger
181
323
  *
@@ -190,12 +332,10 @@ class APIController {
190
332
  * name: tenant
191
333
  * type: string
192
334
  * description: Tenant
193
- * example: boxyhq.com
194
335
  * - in: query
195
336
  * name: product
196
337
  * type: string
197
338
  * description: Product
198
- * example: demo
199
339
  * - in: query
200
340
  * name: clientID
201
341
  * type: string
@@ -205,11 +345,35 @@ class APIController {
205
345
  * description: Success
206
346
  * schema:
207
347
  * type: object
208
- * properties:
209
- * provider:
210
- * type: string
211
348
  * example:
212
- * type: accounts.google.com
349
+ * {
350
+ * "config": {
351
+ * "idpMetadata": {
352
+ * "sso": {
353
+ * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
354
+ * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
355
+ * },
356
+ * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
357
+ * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
358
+ * "loginType": "idp",
359
+ * "provider": "okta.com"
360
+ * },
361
+ * "defaultRedirectUrl": "https://hoppscotch.io/",
362
+ * "redirectUrl": ["https://hoppscotch.io/"],
363
+ * "tenant": "hoppscotch.io",
364
+ * "product": "API Engine",
365
+ * "name": "Hoppscotch-SP",
366
+ * "description": "SP for hoppscotch.io",
367
+ * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
368
+ * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
369
+ * "certs": {
370
+ * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
371
+ * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
372
+ * }
373
+ * }
374
+ * }
375
+ * '400':
376
+ * description: Please provide `clientID` or `tenant` and `product`.
213
377
  * '401':
214
378
  * description: Unauthorized
215
379
  */
@@ -219,7 +383,7 @@ class APIController {
219
383
  metrics.increment('getConfig');
220
384
  if (clientID) {
221
385
  const samlConfig = yield this.configStore.get(clientID);
222
- return samlConfig ? { provider: samlConfig.idpMetadata.provider } : {};
386
+ return samlConfig ? { config: samlConfig } : {};
223
387
  }
224
388
  if (tenant && product) {
225
389
  const samlConfigs = yield this.configStore.getByIndex({
@@ -229,7 +393,7 @@ class APIController {
229
393
  if (!samlConfigs || !samlConfigs.length) {
230
394
  return {};
231
395
  }
232
- return { provider: samlConfigs[0].idpMetadata.provider };
396
+ return { config: samlConfigs[0] };
233
397
  }
234
398
  throw new error_1.JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
235
399
  });
@@ -259,15 +423,15 @@ class APIController {
259
423
  * in: formData
260
424
  * type: string
261
425
  * description: Tenant
262
- * example: boxyhq.com
263
426
  * - name: product
264
427
  * in: formData
265
428
  * type: string
266
429
  * description: Product
267
- * example: demo
268
430
  * responses:
269
431
  * '200':
270
432
  * description: Success
433
+ * '400':
434
+ * description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.'
271
435
  * '401':
272
436
  * description: Unauthorized
273
437
  */
@@ -284,7 +448,7 @@ class APIController {
284
448
  yield this.configStore.delete(clientID);
285
449
  }
286
450
  else {
287
- throw new error_1.JacksonError('clientSecret mismatch.', 400);
451
+ throw new error_1.JacksonError('clientSecret mismatch', 400);
288
452
  }
289
453
  return;
290
454
  }
package/dist/db/db.d.ts CHANGED
@@ -4,6 +4,7 @@ declare class DB implements DatabaseDriver {
4
4
  private encryptionKey;
5
5
  constructor(db: DatabaseDriver, encryptionKey: EncryptionKey);
6
6
  get(namespace: string, key: string): Promise<unknown>;
7
+ getAll(namespace: any): Promise<unknown[]>;
7
8
  getByIndex(namespace: string, idx: Index): Promise<unknown[]>;
8
9
  put(namespace: string, key: string, val: unknown, ttl?: number, ...indexes: Index[]): Promise<unknown>;
9
10
  delete(namespace: string, key: string): Promise<unknown>;
package/dist/db/db.js CHANGED
@@ -57,6 +57,15 @@ class DB {
57
57
  return decrypt(res, this.encryptionKey);
58
58
  });
59
59
  }
60
+ getAll(namespace) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const res = (yield this.db.getAll(namespace));
63
+ const encryptionKey = this.encryptionKey;
64
+ return res.map((r) => {
65
+ return decrypt(r, encryptionKey);
66
+ });
67
+ });
68
+ }
60
69
  getByIndex(namespace, idx) {
61
70
  return __awaiter(this, void 0, void 0, function* () {
62
71
  const res = yield this.db.getByIndex(namespace, idx);
package/dist/db/mem.d.ts CHANGED
@@ -10,6 +10,7 @@ declare class Mem implements DatabaseDriver {
10
10
  constructor(options: DatabaseOption);
11
11
  init(): Promise<Mem>;
12
12
  get(namespace: string, key: string): Promise<any>;
13
+ getAll(namespace: string): Promise<unknown[]>;
13
14
  getByIndex(namespace: string, idx: Index): Promise<any>;
14
15
  put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<any>;
15
16
  delete(namespace: string, key: string): Promise<any>;
package/dist/db/mem.js CHANGED
@@ -66,6 +66,21 @@ class Mem {
66
66
  return null;
67
67
  });
68
68
  }
69
+ getAll(namespace) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ const returnValue = [];
72
+ if (namespace) {
73
+ for (const key in this.store) {
74
+ if (key.startsWith(namespace)) {
75
+ returnValue.push(this.store[key]);
76
+ }
77
+ }
78
+ }
79
+ if (returnValue)
80
+ return returnValue;
81
+ return [];
82
+ });
83
+ }
69
84
  getByIndex(namespace, idx) {
70
85
  return __awaiter(this, void 0, void 0, function* () {
71
86
  const dbKeys = yield this.indexes[dbutils.keyForIndex(namespace, idx)];
@@ -80,6 +95,10 @@ class Mem {
80
95
  return __awaiter(this, void 0, void 0, function* () {
81
96
  const k = dbutils.key(namespace, key);
82
97
  this.store[k] = val;
98
+ if (!Date.parse(this.store['createdAt']))
99
+ this.store['createdAt'] = new Date().toISOString();
100
+ this.store['modifiedAt'] = new Date().toISOString();
101
+ // console.log(this.store)
83
102
  if (ttl) {
84
103
  this.ttlStore[k] = {
85
104
  namespace,
@@ -7,6 +7,7 @@ declare class Mongo implements DatabaseDriver {
7
7
  constructor(options: DatabaseOption);
8
8
  init(): Promise<Mongo>;
9
9
  get(namespace: string, key: string): Promise<any>;
10
+ getAll(namespace: string): Promise<unknown[]>;
10
11
  getByIndex(namespace: string, idx: Index): Promise<any>;
11
12
  put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
12
13
  delete(namespace: string, key: string): Promise<any>;
package/dist/db/mongo.js CHANGED
@@ -36,8 +36,16 @@ class Mongo {
36
36
  }
37
37
  init() {
38
38
  return __awaiter(this, void 0, void 0, function* () {
39
- this.client = new mongodb_1.MongoClient(this.options.url);
40
- yield this.client.connect();
39
+ try {
40
+ if (!this.options.url) {
41
+ throw Error('Please specify a db url');
42
+ }
43
+ this.client = new mongodb_1.MongoClient(this.options.url);
44
+ yield this.client.connect();
45
+ }
46
+ catch (err) {
47
+ console.error(`error connecting to ${this.options.type} db: ${err}`);
48
+ }
41
49
  this.db = this.client.db();
42
50
  this.collection = this.db.collection('jacksonStore');
43
51
  yield this.collection.createIndex({ indexes: 1 });
@@ -56,6 +64,15 @@ class Mongo {
56
64
  return null;
57
65
  });
58
66
  }
67
+ getAll(namespace) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ const _namespaceMatch = new RegExp(`^${namespace}:.*`);
70
+ const docs = yield this.collection.find({ _id: _namespaceMatch }).toArray();
71
+ if (docs)
72
+ return docs.map(({ value }) => value);
73
+ return [];
74
+ });
75
+ }
59
76
  getByIndex(namespace, idx) {
60
77
  return __awaiter(this, void 0, void 0, function* () {
61
78
  const docs = yield this.collection
@@ -86,8 +103,12 @@ class Mongo {
86
103
  }
87
104
  doc.indexes.push(idxKey);
88
105
  }
106
+ doc.modifiedAt = new Date().toISOString();
89
107
  yield this.collection.updateOne({ _id: dbutils.key(namespace, key) }, {
90
108
  $set: doc,
109
+ $setOnInsert: {
110
+ createdAt: new Date().toISOString(),
111
+ },
91
112
  }, { upsert: true });
92
113
  });
93
114
  }
@@ -5,6 +5,7 @@ declare class Redis implements DatabaseDriver {
5
5
  constructor(options: DatabaseOption);
6
6
  init(): Promise<Redis>;
7
7
  get(namespace: string, key: string): Promise<any>;
8
+ getAll(namespace: string): Promise<unknown[]>;
8
9
  getByIndex(namespace: string, idx: Index): Promise<any>;
9
10
  put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
10
11
  delete(namespace: string, key: string): Promise<any>;
package/dist/db/redis.js CHANGED
@@ -57,6 +57,26 @@ class Redis {
57
57
  return null;
58
58
  });
59
59
  }
60
+ getAll(namespace) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const keys = yield this.client.sendCommand(['keys', namespace + ':*']);
63
+ const returnValue = [];
64
+ for (let i = 0; i < keys.length; i++) {
65
+ try {
66
+ if (this.client.get(keys[i])) {
67
+ const value = yield this.client.get(keys[i]);
68
+ returnValue.push(JSON.parse(value));
69
+ }
70
+ }
71
+ catch (error) {
72
+ console.error(error);
73
+ }
74
+ }
75
+ if (returnValue)
76
+ return returnValue;
77
+ return [];
78
+ });
79
+ }
60
80
  getByIndex(namespace, idx) {
61
81
  return __awaiter(this, void 0, void 0, function* () {
62
82
  const dbKeys = yield this.client.sMembers(dbutils.keyForIndex(namespace, idx));
@@ -3,4 +3,6 @@ export declare class JacksonStore {
3
3
  value: string;
4
4
  iv?: string;
5
5
  tag?: string;
6
+ createdAt?: Date;
7
+ modifiedAt?: string;
6
8
  }
@@ -36,6 +36,19 @@ __decorate([
36
36
  nullable: true,
37
37
  })
38
38
  ], JacksonStore.prototype, "tag", void 0);
39
+ __decorate([
40
+ (0, typeorm_1.Column)({
41
+ type: 'timestamp',
42
+ default: () => 'CURRENT_TIMESTAMP',
43
+ nullable: false,
44
+ })
45
+ ], JacksonStore.prototype, "createdAt", void 0);
46
+ __decorate([
47
+ (0, typeorm_1.Column)({
48
+ type: 'timestamp',
49
+ nullable: true,
50
+ })
51
+ ], JacksonStore.prototype, "modifiedAt", void 0);
39
52
  JacksonStore = __decorate([
40
53
  (0, typeorm_1.Entity)()
41
54
  ], JacksonStore);
@@ -10,6 +10,7 @@ declare class Sql implements DatabaseDriver {
10
10
  constructor(options: DatabaseOption);
11
11
  init(): Promise<Sql>;
12
12
  get(namespace: string, key: string): Promise<any>;
13
+ getAll(namespace: string): Promise<unknown[]>;
13
14
  getByIndex(namespace: string, idx: Index): Promise<any>;
14
15
  put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
15
16
  delete(namespace: string, key: string): Promise<any>;
@@ -95,7 +95,7 @@ class Sql {
95
95
  }
96
96
  get(namespace, key) {
97
97
  return __awaiter(this, void 0, void 0, function* () {
98
- let res = yield this.storeRepository.findOne({
98
+ const res = yield this.storeRepository.findOne({
99
99
  key: dbutils.key(namespace, key),
100
100
  });
101
101
  if (res && res.value) {
@@ -108,6 +108,22 @@ class Sql {
108
108
  return null;
109
109
  });
110
110
  }
111
+ getAll(namespace) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ const response = yield this.storeRepository.find({
114
+ where: { key: (0, typeorm_1.Like)(`%${namespace}%`) },
115
+ select: ['value', 'iv', 'tag'],
116
+ order: {
117
+ ['createdAt']: 'DESC',
118
+ // ['createdAt']: 'ASC',
119
+ },
120
+ });
121
+ const returnValue = JSON.parse(JSON.stringify(response));
122
+ if (returnValue)
123
+ return returnValue;
124
+ return [];
125
+ });
126
+ }
111
127
  getByIndex(namespace, idx) {
112
128
  return __awaiter(this, void 0, void 0, function* () {
113
129
  const res = yield this.indexRepository.find({
@@ -135,6 +151,7 @@ class Sql {
135
151
  store.value = val.value;
136
152
  store.iv = val.iv;
137
153
  store.tag = val.tag;
154
+ store.modifiedAt = new Date().toISOString();
138
155
  yield transactionalEntityManager.save(store);
139
156
  if (ttl) {
140
157
  const ttlRec = new JacksonTTL_1.JacksonTTL();
package/dist/db/store.js CHANGED
@@ -40,6 +40,11 @@ class Store {
40
40
  return yield this.db.get(this.namespace, dbutils.keyDigest(key));
41
41
  });
42
42
  }
43
+ getAll() {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ return yield this.db.getAll(this.namespace);
46
+ });
47
+ }
43
48
  getByIndex(idx) {
44
49
  return __awaiter(this, void 0, void 0, function* () {
45
50
  idx.value = dbutils.keyDigest(idx.value);
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { APIController } from './controller/api';
2
2
  import { OAuthController } from './controller/oauth';
3
+ import { AdminController } from './controller/admin';
3
4
  import { JacksonOption } from './typings';
4
5
  export declare const controllers: (opts: JacksonOption) => Promise<{
5
6
  apiController: APIController;
6
7
  oauthController: OAuthController;
8
+ adminController: AdminController;
7
9
  }>;
8
10
  export default controllers;
9
11
  export * from './typings';
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.controllers = void 0;
26
26
  const api_1 = require("./controller/api");
27
27
  const oauth_1 = require("./controller/oauth");
28
+ const admin_1 = require("./controller/admin");
28
29
  const db_1 = __importDefault(require("./db/db"));
29
30
  const read_config_1 = __importDefault(require("./read-config"));
30
31
  const defaultOpts = (opts) => {
@@ -55,6 +56,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
55
56
  const codeStore = db.store('oauth:code', opts.db.ttl);
56
57
  const tokenStore = db.store('oauth:token', opts.db.ttl);
57
58
  const apiController = new api_1.APIController({ configStore });
59
+ const adminController = new admin_1.AdminController({ configStore });
58
60
  const oauthController = new oauth_1.OAuthController({
59
61
  configStore,
60
62
  sessionStore,
@@ -75,6 +77,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
75
77
  return {
76
78
  apiController,
77
79
  oauthController,
80
+ adminController,
78
81
  };
79
82
  });
80
83
  exports.controllers = controllers;
package/dist/saml/saml.js CHANGED
@@ -55,17 +55,7 @@ function PubKeyInfo(pubKey) {
55
55
  this.getKeyInfo = function (_key, prefix) {
56
56
  prefix = prefix || '';
57
57
  prefix = prefix ? prefix + ':' : prefix;
58
- return ('<' +
59
- prefix +
60
- 'X509Data><' +
61
- prefix +
62
- 'X509Certificate>' +
63
- this.pubKey +
64
- '</' +
65
- prefix +
66
- 'X509Certificate></' +
67
- prefix +
68
- 'X509Data>');
58
+ return `<${prefix}X509Data><${prefix}X509Certificate>${this.pubKey}</${prefix}X509Certificate</${prefix}X509Data>`;
69
59
  };
70
60
  }
71
61
  const signRequest = (xml, signingKey, publicKey) => {
package/dist/typings.d.ts CHANGED
@@ -3,6 +3,8 @@ export declare type IdPConfig = {
3
3
  redirectUrl: string;
4
4
  tenant: string;
5
5
  product: string;
6
+ name: string;
7
+ description: string;
6
8
  rawMetadata?: string;
7
9
  encodedRawMetadata?: string;
8
10
  };
@@ -13,11 +15,12 @@ export interface OAuth {
13
15
  }
14
16
  export interface IAPIController {
15
17
  config(body: IdPConfig): Promise<OAuth>;
18
+ updateConfig(body: any): Promise<void>;
16
19
  getConfig(body: {
17
20
  clientID?: string;
18
21
  tenant?: string;
19
22
  product?: string;
20
- }): Promise<Partial<OAuth>>;
23
+ }): Promise<any>;
21
24
  deleteConfig(body: {
22
25
  clientID?: string;
23
26
  clientSecret?: string;
@@ -36,6 +39,9 @@ export interface IOAuthController {
36
39
  token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
37
40
  userInfo(token: string): Promise<Profile>;
38
41
  }
42
+ export interface IAdminController {
43
+ getAllConfig(): any;
44
+ }
39
45
  export interface OAuthReqBody {
40
46
  response_type: 'code';
41
47
  client_id: string;
@@ -74,12 +80,14 @@ export interface Index {
74
80
  value: string;
75
81
  }
76
82
  export interface DatabaseDriver {
83
+ getAll(namespace: string): Promise<unknown[]>;
77
84
  get(namespace: string, key: string): Promise<any>;
78
85
  put(namespace: string, key: string, val: any, ttl: number, ...indexes: Index[]): Promise<any>;
79
86
  delete(namespace: string, key: string): Promise<any>;
80
87
  getByIndex(namespace: string, idx: Index): Promise<any>;
81
88
  }
82
89
  export interface Storable {
90
+ getAll(): Promise<unknown[]>;
83
91
  get(key: string): Promise<any>;
84
92
  put(key: string, val: any, ...indexes: Index[]): Promise<any>;
85
93
  delete(key: string): Promise<any>;
@@ -0,0 +1,16 @@
1
+ import {MigrationInterface, QueryRunner} from "typeorm";
2
+
3
+ export class createdAt1644332636666 implements MigrationInterface {
4
+ name = 'createdAt1644332636666'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`createdAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()`);
8
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`modifiedAt\` timestamp NULL`);
9
+ }
10
+
11
+ public async down(queryRunner: QueryRunner): Promise<void> {
12
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`modifiedAt\``);
13
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`createdAt\``);
14
+ }
15
+
16
+ }
@@ -0,0 +1,16 @@
1
+ import {MigrationInterface, QueryRunner} from "typeorm";
2
+
3
+ export class createdAt1644332641078 implements MigrationInterface {
4
+ name = 'createdAt1644332641078'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`createdAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP`);
8
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`modifiedAt\` timestamp NULL`);
9
+ }
10
+
11
+ public async down(queryRunner: QueryRunner): Promise<void> {
12
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`modifiedAt\``);
13
+ await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`createdAt\``);
14
+ }
15
+
16
+ }
@@ -0,0 +1,16 @@
1
+ import {MigrationInterface, QueryRunner} from "typeorm";
2
+
3
+ export class createdAt1644332647279 implements MigrationInterface {
4
+ name = 'createdAt1644332647279'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`ALTER TABLE "jackson_store" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`);
8
+ await queryRunner.query(`ALTER TABLE "jackson_store" ADD "modifiedAt" TIMESTAMP`);
9
+ }
10
+
11
+ public async down(queryRunner: QueryRunner): Promise<void> {
12
+ await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "modifiedAt"`);
13
+ await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "createdAt"`);
14
+ }
15
+
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "SAML Jackson library",
5
5
  "keywords": [
6
6
  "SAML 2.0"
@@ -18,9 +18,9 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "build": "tsc -p tsconfig.build.json",
21
- "db:migration:generate:postgres": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n Initial",
22
- "db:migration:generate:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n Initial",
23
- "db:migration:generate:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n Initial",
21
+ "db:migration:generate:postgres": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n createdAt",
22
+ "db:migration:generate:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n createdAt",
23
+ "db:migration:generate:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n createdAt",
24
24
  "db:migration:run:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",
25
25
  "db:migration:run:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",
26
26
  "db:migration:run:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",