@boxyhq/saml-jackson 0.3.7-beta.678 → 0.3.7-beta.682

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,10 +18,24 @@ 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
+ * required: true
26
+ * example: cal-saml-config
27
+ * - name: description
28
+ * description: A short description for the config not more than 50 characters
29
+ * type: string
30
+ * in: formData
31
+ * example: SAML login for cal.com app
21
32
  * - name: encodedRawMetadata
22
33
  * description: Base64 encoding of the XML metadata
23
34
  * in: formData
24
- * required: true
35
+ * type: string
36
+ * - name: rawMetadata
37
+ * description: Raw XML metadata
38
+ * in: formData
25
39
  * type: string
26
40
  * - name: defaultRedirectUrl
27
41
  * description: The redirect URL to use in the IdP login flow
@@ -67,6 +81,80 @@ export declare class APIController implements IAPIController {
67
81
  * description: Unauthorized
68
82
  */
69
83
  config(body: IdPConfig): Promise<OAuth>;
84
+ /**
85
+ * @swagger
86
+ *
87
+ * /api/v1/saml/config:
88
+ * patch:
89
+ * summary: Update SAML configuration
90
+ * operationId: update-saml-config
91
+ * tags: [SAML Config]
92
+ * consumes:
93
+ * - application/json
94
+ * - application/x-www-form-urlencoded
95
+ * parameters:
96
+ * - name: clientID
97
+ * description: Client ID for the config
98
+ * type: string
99
+ * in: formData
100
+ * required: true
101
+ * - name: clientSecret
102
+ * description: Client Secret for the config
103
+ * type: string
104
+ * in: formData
105
+ * required: true
106
+ * - name: name
107
+ * description: Name/identifier for the config
108
+ * type: string
109
+ * in: formData
110
+ * required: true
111
+ * example: cal-saml-config
112
+ * - name: description
113
+ * description: A short description for the config not more than 50 characters
114
+ * type: string
115
+ * in: formData
116
+ * example: SAML login for cal.com app
117
+ * - name: encodedRawMetadata
118
+ * description: Base64 encoding of the XML metadata
119
+ * in: formData
120
+ * type: string
121
+ * - name: rawMetadata
122
+ * description: Raw XML metadata
123
+ * in: formData
124
+ * type: string
125
+ * - name: defaultRedirectUrl
126
+ * description: The redirect URL to use in the IdP login flow
127
+ * in: formData
128
+ * required: true
129
+ * type: string
130
+ * example: http://localhost:3000/login/saml
131
+ * - name: redirectUrl
132
+ * description: JSON encoded array containing a list of allowed redirect URLs
133
+ * in: formData
134
+ * required: true
135
+ * type: string
136
+ * example: '["http://localhost:3000/*"]'
137
+ * - name: tenant
138
+ * description: Tenant
139
+ * in: formData
140
+ * required: true
141
+ * type: string
142
+ * example: boxyhq.com
143
+ * - name: product
144
+ * description: Product
145
+ * in: formData
146
+ * required: true
147
+ * type: string
148
+ * example: demo
149
+ * responses:
150
+ * 204:
151
+ * description: Success
152
+ * 400:
153
+ * description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata
154
+ * 401:
155
+ * description: Unauthorized
156
+ */
157
+ updateConfig(body: any): Promise<void>;
70
158
  /**
71
159
  * @swagger
72
160
  *
@@ -96,11 +184,35 @@ export declare class APIController implements IAPIController {
96
184
  * description: Success
97
185
  * schema:
98
186
  * type: object
99
- * properties:
100
- * provider:
101
- * type: string
102
187
  * example:
103
- * type: accounts.google.com
188
+ * {
189
+ * "config": {
190
+ * "idpMetadata": {
191
+ * "sso": {
192
+ * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
193
+ * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
194
+ * },
195
+ * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
196
+ * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
197
+ * "loginType": "idp",
198
+ * "provider": "okta.com"
199
+ * },
200
+ * "defaultRedirectUrl": "https://hoppscotch.io/",
201
+ * "redirectUrl": ["https://hoppscotch.io/"],
202
+ * "tenant": "hoppscotch.io",
203
+ * "product": "API Engine",
204
+ * "name": "Hoppscotch-SP",
205
+ * "description": "SP for hoppscotch.io",
206
+ * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
207
+ * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
208
+ * "certs": {
209
+ * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
210
+ * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
211
+ * }
212
+ * }
213
+ * }
214
+ * '400':
215
+ * description: Please provide `clientID` or `tenant`/`product`.
104
216
  * '401':
105
217
  * description: Unauthorized
106
218
  */
@@ -108,7 +220,7 @@ export declare class APIController implements IAPIController {
108
220
  clientID: string;
109
221
  tenant: string;
110
222
  product: string;
111
- }): Promise<Partial<OAuth>>;
223
+ }): Promise<any>;
112
224
  /**
113
225
  * @swagger
114
226
  * /api/v1/saml/config:
@@ -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
  };
@@ -43,7 +54,7 @@ class APIController {
43
54
  this.configStore = configStore;
44
55
  }
45
56
  _validateIdPConfig(body) {
46
- const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
57
+ const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name } = body;
47
58
  if (!rawMetadata && !encodedRawMetadata) {
48
59
  throw new error_1.JacksonError('Please provide rawMetadata or encodedRawMetadata', 400);
49
60
  }
@@ -59,6 +70,9 @@ class APIController {
59
70
  if (!product) {
60
71
  throw new error_1.JacksonError('Please provide product', 400);
61
72
  }
73
+ if (!name) {
74
+ throw new error_1.JacksonError('Please provide a friendly name', 400);
75
+ }
62
76
  }
63
77
  /**
64
78
  * @swagger
@@ -73,10 +87,24 @@ class APIController {
73
87
  * consumes:
74
88
  * - application/x-www-form-urlencoded
75
89
  * parameters:
90
+ * - name: name
91
+ * description: Name/identifier for the config
92
+ * type: string
93
+ * in: formData
94
+ * required: true
95
+ * example: cal-saml-config
96
+ * - name: description
97
+ * description: A short description for the config not more than 50 characters
98
+ * type: string
99
+ * in: formData
100
+ * example: SAML login for cal.com app
76
101
  * - name: encodedRawMetadata
77
102
  * description: Base64 encoding of the XML metadata
78
103
  * in: formData
79
- * required: true
104
+ * type: string
105
+ * - name: rawMetadata
106
+ * description: Raw XML metadata
107
+ * in: formData
80
108
  * type: string
81
109
  * - name: defaultRedirectUrl
82
110
  * description: The redirect URL to use in the IdP login flow
@@ -123,7 +151,7 @@ class APIController {
123
151
  */
124
152
  config(body) {
125
153
  return __awaiter(this, void 0, void 0, function* () {
126
- const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
154
+ const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name, description, } = body;
127
155
  this._validateIdPConfig(body);
128
156
  let metaData = rawMetadata;
129
157
  if (encodedRawMetadata) {
@@ -155,6 +183,8 @@ class APIController {
155
183
  redirectUrl: JSON.parse(redirectUrl),
156
184
  tenant,
157
185
  product,
186
+ name,
187
+ description,
158
188
  clientID,
159
189
  clientSecret,
160
190
  certs,
@@ -174,6 +204,127 @@ class APIController {
174
204
  };
175
205
  });
176
206
  }
207
+ /**
208
+ * @swagger
209
+ *
210
+ * /api/v1/saml/config:
211
+ * patch:
212
+ * summary: Update SAML configuration
213
+ * operationId: update-saml-config
214
+ * tags: [SAML Config]
215
+ * consumes:
216
+ * - application/json
217
+ * - application/x-www-form-urlencoded
218
+ * parameters:
219
+ * - name: clientID
220
+ * description: Client ID for the config
221
+ * type: string
222
+ * in: formData
223
+ * required: true
224
+ * - name: clientSecret
225
+ * description: Client Secret for the config
226
+ * type: string
227
+ * in: formData
228
+ * required: true
229
+ * - name: name
230
+ * description: Name/identifier for the config
231
+ * type: string
232
+ * in: formData
233
+ * required: true
234
+ * example: cal-saml-config
235
+ * - name: description
236
+ * description: A short description for the config not more than 50 characters
237
+ * type: string
238
+ * in: formData
239
+ * example: SAML login for cal.com app
240
+ * - name: encodedRawMetadata
241
+ * description: Base64 encoding of the XML metadata
242
+ * in: formData
243
+ * type: string
244
+ * - name: rawMetadata
245
+ * description: Raw XML metadata
246
+ * in: formData
247
+ * type: string
248
+ * - name: defaultRedirectUrl
249
+ * description: The redirect URL to use in the IdP login flow
250
+ * in: formData
251
+ * required: true
252
+ * type: string
253
+ * example: http://localhost:3000/login/saml
254
+ * - name: redirectUrl
255
+ * description: JSON encoded array containing a list of allowed redirect URLs
256
+ * in: formData
257
+ * required: true
258
+ * type: string
259
+ * example: '["http://localhost:3000/*"]'
260
+ * - name: tenant
261
+ * description: Tenant
262
+ * in: formData
263
+ * required: true
264
+ * type: string
265
+ * example: boxyhq.com
266
+ * - name: product
267
+ * description: Product
268
+ * in: formData
269
+ * required: true
270
+ * type: string
271
+ * example: demo
272
+ * responses:
273
+ * 204:
274
+ * description: Success
275
+ * 400:
276
+ * description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata
277
+ * 401:
278
+ * description: Unauthorized
279
+ */
280
+ updateConfig(body) {
281
+ var _a;
282
+ return __awaiter(this, void 0, void 0, function* () {
283
+ const { encodedRawMetadata, // could be empty
284
+ rawMetadata, // could be empty
285
+ defaultRedirectUrl, redirectUrl, name, description } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description"]);
286
+ if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
287
+ throw new error_1.JacksonError('Please provide clientID', 400);
288
+ }
289
+ if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
290
+ throw new error_1.JacksonError('Please provide clientSecret', 400);
291
+ }
292
+ const _currentConfig = (_a = (yield this.getConfig(clientInfo))) === null || _a === void 0 ? void 0 : _a.config;
293
+ if (_currentConfig.clientSecret !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
294
+ throw new error_1.JacksonError('clientSecret mismatch', 400);
295
+ }
296
+ let metaData = rawMetadata;
297
+ if (encodedRawMetadata) {
298
+ metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
299
+ }
300
+ let newMetadata;
301
+ if (metaData) {
302
+ newMetadata = yield saml_1.default.parseMetadataAsync(metaData);
303
+ // extract provider
304
+ let providerName = extractHostName(newMetadata.entityID);
305
+ if (!providerName) {
306
+ providerName = extractHostName(newMetadata.sso.redirectUrl || newMetadata.sso.postUrl);
307
+ }
308
+ newMetadata.provider = providerName ? providerName : 'Unknown';
309
+ }
310
+ if (newMetadata) {
311
+ // check if clientID matches with new metadata payload
312
+ const clientID = dbutils.keyDigest(dbutils.keyFromParts(clientInfo.tenant, clientInfo.product, newMetadata.entityID));
313
+ if (clientID !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
314
+ throw new error_1.JacksonError('Tenant/Product config mismatch with IdP metadata', 400);
315
+ }
316
+ }
317
+ 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 }), {
318
+ // secondary index on entityID
319
+ name: utils_1.IndexNames.EntityID,
320
+ value: _currentConfig.idpMetadata.entityID,
321
+ }, {
322
+ // secondary index on tenant + product
323
+ name: utils_1.IndexNames.TenantProduct,
324
+ value: dbutils.keyFromParts(_currentConfig.tenant, _currentConfig.product),
325
+ });
326
+ });
327
+ }
177
328
  /**
178
329
  * @swagger
179
330
  *
@@ -203,11 +354,35 @@ class APIController {
203
354
  * description: Success
204
355
  * schema:
205
356
  * type: object
206
- * properties:
207
- * provider:
208
- * type: string
209
357
  * example:
210
- * type: accounts.google.com
358
+ * {
359
+ * "config": {
360
+ * "idpMetadata": {
361
+ * "sso": {
362
+ * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
363
+ * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
364
+ * },
365
+ * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
366
+ * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
367
+ * "loginType": "idp",
368
+ * "provider": "okta.com"
369
+ * },
370
+ * "defaultRedirectUrl": "https://hoppscotch.io/",
371
+ * "redirectUrl": ["https://hoppscotch.io/"],
372
+ * "tenant": "hoppscotch.io",
373
+ * "product": "API Engine",
374
+ * "name": "Hoppscotch-SP",
375
+ * "description": "SP for hoppscotch.io",
376
+ * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
377
+ * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
378
+ * "certs": {
379
+ * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
380
+ * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
381
+ * }
382
+ * }
383
+ * }
384
+ * '400':
385
+ * description: Please provide `clientID` or `tenant`/`product`.
211
386
  * '401':
212
387
  * description: Unauthorized
213
388
  */
@@ -216,7 +391,7 @@ class APIController {
216
391
  const { clientID, tenant, product } = body;
217
392
  if (clientID) {
218
393
  const samlConfig = yield this.configStore.get(clientID);
219
- return samlConfig ? { provider: samlConfig.idpMetadata.provider } : {};
394
+ return samlConfig ? { config: samlConfig } : {};
220
395
  }
221
396
  if (tenant && product) {
222
397
  const samlConfigs = yield this.configStore.getByIndex({
@@ -226,7 +401,7 @@ class APIController {
226
401
  if (!samlConfigs || !samlConfigs.length) {
227
402
  return {};
228
403
  }
229
- return { provider: samlConfigs[0].idpMetadata.provider };
404
+ return { config: samlConfigs[0] };
230
405
  }
231
406
  throw new error_1.JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
232
407
  });
@@ -280,7 +455,7 @@ class APIController {
280
455
  yield this.configStore.delete(clientID);
281
456
  }
282
457
  else {
283
- throw new error_1.JacksonError('clientSecret mismatch.', 400);
458
+ throw new error_1.JacksonError('clientSecret mismatch', 400);
284
459
  }
285
460
  return;
286
461
  }
@@ -292,12 +292,6 @@ class OAuthController {
292
292
  throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
293
293
  }
294
294
  }
295
- else {
296
- // encoded client_id, verify client_secret
297
- if (client_secret !== this.opts.clientSecretVerifier) {
298
- throw new error_1.JacksonError('Invalid client_secret', 401);
299
- }
300
- }
301
295
  }
302
296
  }
303
297
  else if (codeVal && codeVal.session) {
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 { JacksonOption } from './typings';
2
2
  import { APIController } from './controller/api';
3
3
  import { OAuthController } from './controller/oauth';
4
+ import { AdminController } from './controller/admin';
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) => {
@@ -44,7 +45,6 @@ const defaultOpts = (opts) => {
44
45
  newOpts.db.type = newOpts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql.
45
46
  newOpts.db.ttl = (newOpts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
46
47
  newOpts.db.cleanupLimit = (newOpts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
47
- newOpts.clientSecretVerifier = newOpts.clientSecretVerifier || 'dummy';
48
48
  return newOpts;
49
49
  };
50
50
  const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
@@ -55,6 +55,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
55
55
  const codeStore = db.store('oauth:code', opts.db.ttl);
56
56
  const tokenStore = db.store('oauth:token', opts.db.ttl);
57
57
  const apiController = new api_1.APIController({ configStore });
58
+ const adminController = new admin_1.AdminController({ configStore });
58
59
  const oauthController = new oauth_1.OAuthController({
59
60
  configStore,
60
61
  sessionStore,
@@ -75,6 +76,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
75
76
  return {
76
77
  apiController,
77
78
  oauthController,
79
+ adminController,
78
80
  };
79
81
  });
80
82
  exports.controllers = controllers;
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;
@@ -35,6 +38,9 @@ export interface IOAuthController {
35
38
  token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
36
39
  userInfo(token: string): Promise<Profile>;
37
40
  }
41
+ export interface IAdminController {
42
+ getAllConfig(): any;
43
+ }
38
44
  export interface OAuthReqBody {
39
45
  response_type: 'code';
40
46
  client_id: string;
@@ -73,12 +79,14 @@ export interface Index {
73
79
  value: string;
74
80
  }
75
81
  export interface DatabaseDriver {
82
+ getAll(namespace: string): Promise<unknown[]>;
76
83
  get(namespace: string, key: string): Promise<any>;
77
84
  put(namespace: string, key: string, val: any, ttl: number, ...indexes: Index[]): Promise<any>;
78
85
  delete(namespace: string, key: string): Promise<any>;
79
86
  getByIndex(namespace: string, idx: Index): Promise<any>;
80
87
  }
81
88
  export interface Storable {
89
+ getAll(): Promise<unknown[]>;
82
90
  get(key: string): Promise<any>;
83
91
  put(key: string, val: any, ...indexes: Index[]): Promise<any>;
84
92
  delete(key: string): Promise<any>;
@@ -123,5 +131,4 @@ export interface JacksonOption {
123
131
  preLoadedConfig?: string;
124
132
  idpEnabled?: boolean;
125
133
  db: DatabaseOption;
126
- clientSecretVerifier?: string;
127
134
  }
@@ -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.3.7-beta.678",
3
+ "version": "0.3.7-beta.682",
4
4
  "description": "SAML 2.0 service",
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",