@boxyhq/saml-jackson 1.2.1 → 1.3.0

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.
@@ -31,392 +31,531 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31
31
  step((generator = generator.apply(thisArg, _arguments || [])).next());
32
32
  });
33
33
  };
34
- var __rest = (this && this.__rest) || function (s, e) {
35
- var t = {};
36
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
37
- t[p] = s[p];
38
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
39
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
40
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
41
- t[p[i]] = s[p[i]];
42
- }
43
- return t;
44
- };
45
34
  var __importDefault = (this && this.__importDefault) || function (mod) {
46
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
36
  };
48
37
  Object.defineProperty(exports, "__esModule", { value: true });
49
- exports.APIController = void 0;
50
- const crypto_1 = __importDefault(require("crypto"));
38
+ exports.ConnectionAPIController = void 0;
51
39
  const dbutils = __importStar(require("../db/utils"));
52
40
  const metrics = __importStar(require("../opentelemetry/metrics"));
53
- const saml20_1 = __importDefault(require("@boxyhq/saml20"));
54
- const x509_1 = __importDefault(require("../saml/x509"));
55
41
  const error_1 = require("./error");
56
42
  const utils_1 = require("./utils");
57
- class APIController {
58
- constructor({ configStore }) {
59
- this.configStore = configStore;
60
- }
61
- _validateRedirectUrl({ redirectUrlList, defaultRedirectUrl }) {
62
- if (redirectUrlList) {
63
- if (redirectUrlList.length > 100) {
64
- throw new error_1.JacksonError('Exceeded maximum number of allowed redirect urls', 400);
65
- }
66
- for (const url of redirectUrlList) {
67
- (0, utils_1.validateAbsoluteUrl)(url, 'redirectUrl is invalid');
68
- }
69
- }
70
- if (defaultRedirectUrl) {
71
- (0, utils_1.validateAbsoluteUrl)(defaultRedirectUrl, 'defaultRedirectUrl is invalid');
72
- }
73
- }
74
- _validateIdPConfig(body) {
75
- const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, description } = body;
76
- if (!rawMetadata && !encodedRawMetadata) {
77
- throw new error_1.JacksonError('Please provide rawMetadata or encodedRawMetadata', 400);
78
- }
79
- if (!defaultRedirectUrl) {
80
- throw new error_1.JacksonError('Please provide a defaultRedirectUrl', 400);
81
- }
82
- if (!redirectUrl) {
83
- throw new error_1.JacksonError('Please provide redirectUrl', 400);
84
- }
85
- if (!tenant) {
86
- throw new error_1.JacksonError('Please provide tenant', 400);
87
- }
88
- if (!product) {
89
- throw new error_1.JacksonError('Please provide product', 400);
90
- }
91
- if (description && description.length > 100) {
92
- throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
93
- }
43
+ const oidc_1 = __importDefault(require("./connection/oidc"));
44
+ const saml_1 = __importDefault(require("./connection/saml"));
45
+ class ConnectionAPIController {
46
+ constructor({ connectionStore }) {
47
+ this.connectionStore = connectionStore;
94
48
  }
95
49
  /**
96
50
  * @swagger
51
+ * definitions:
52
+ * Connection:
53
+ * type: object
54
+ * example:
55
+ * {
56
+ * "idpMetadata": {
57
+ * "sso": {
58
+ * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxsso/saml",
59
+ * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxsso/saml"
60
+ * },
61
+ * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
62
+ * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
63
+ * "loginType": "idp",
64
+ * "provider": "okta.com"
65
+ * },
66
+ * "defaultRedirectUrl": "https://hoppscotch.io/",
67
+ * "redirectUrl": ["https://hoppscotch.io/"],
68
+ * "tenant": "hoppscotch.io",
69
+ * "product": "API Engine",
70
+ * "name": "Hoppscotch-SP",
71
+ * "description": "SP for hoppscotch.io",
72
+ * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
73
+ * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
74
+ * "certs": {
75
+ * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
76
+ * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
77
+ * }
78
+ * }
79
+ * validationErrorsPost:
80
+ * description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters | Strategy&#58; xxxx not supported | Please provide the clientId from OpenID Provider | Please provide the clientSecret from OpenID Provider | Please provide the discoveryUrl for the OpenID Provider
97
81
  *
82
+ * parameters:
83
+ * nameParamPost:
84
+ * name: name
85
+ * description: Name/identifier for the connection
86
+ * type: string
87
+ * in: formData
88
+ * descriptionParamPost:
89
+ * name: description
90
+ * description: A short description for the connection not more than 100 characters
91
+ * type: string
92
+ * in: formData
93
+ * encodedRawMetadataParamPost:
94
+ * name: encodedRawMetadata
95
+ * description: Base64 encoding of the XML metadata
96
+ * in: formData
97
+ * type: string
98
+ * rawMetadataParamPost:
99
+ * name: rawMetadata
100
+ * description: Raw XML metadata
101
+ * in: formData
102
+ * type: string
103
+ * defaultRedirectUrlParamPost:
104
+ * name: defaultRedirectUrl
105
+ * description: The redirect URL to use in the IdP login flow
106
+ * in: formData
107
+ * required: true
108
+ * type: string
109
+ * redirectUrlParamPost:
110
+ * name: redirectUrl
111
+ * description: JSON encoded array containing a list of allowed redirect URLs
112
+ * in: formData
113
+ * required: true
114
+ * type: string
115
+ * tenantParamPost:
116
+ * name: tenant
117
+ * description: Tenant
118
+ * in: formData
119
+ * required: true
120
+ * type: string
121
+ * productParamPost:
122
+ * name: product
123
+ * description: Product
124
+ * in: formData
125
+ * required: true
126
+ * type: string
127
+ * oidcDiscoveryUrlPost:
128
+ * name: oidcDiscoveryUrl
129
+ * description: well-known URL where the OpenID Provider configuration is exposed
130
+ * in: formData
131
+ * type: string
132
+ * oidcClientIdPost:
133
+ * name: oidcClientId
134
+ * description: clientId of the application set up on the OpenID Provider
135
+ * in: formData
136
+ * type: string
137
+ * oidcClientSecretPost:
138
+ * name: oidcClientSecret
139
+ * description: clientSecret of the application set up on the OpenID Provider
140
+ * in: formData
141
+ * type: string
98
142
  * /api/v1/saml/config:
99
143
  * post:
100
- * summary: Create SAML configuration
101
- * operationId: create-saml-config
102
- * tags: [SAML Config]
144
+ * summary: Create SAML config
145
+ * operationId: create-saml-config
146
+ * deprecated: true
147
+ * tags: [SAML Config - Deprecated]
148
+ * produces:
149
+ * - application/json
150
+ * consumes:
151
+ * - application/x-www-form-urlencoded
152
+ * - application/json
153
+ * parameters:
154
+ * - $ref: '#/parameters/nameParamPost'
155
+ * - $ref: '#/parameters/descriptionParamPost'
156
+ * - $ref: '#/parameters/encodedRawMetadataParamPost'
157
+ * - $ref: '#/parameters/rawMetadataParamPost'
158
+ * - $ref: '#/parameters/defaultRedirectUrlParamPost'
159
+ * - $ref: '#/parameters/redirectUrlParamPost'
160
+ * - $ref: '#/parameters/tenantParamPost'
161
+ * - $ref: '#/parameters/productParamPost'
162
+ * responses:
163
+ * 200:
164
+ * description: Success
165
+ * schema:
166
+ * $ref: '#/definitions/Connection'
167
+ * 400:
168
+ * $ref: '#/definitions/validationErrorsPost'
169
+ * 401:
170
+ * description: Unauthorized
171
+ * /api/v1/connections:
172
+ * post:
173
+ * summary: Create SSO connection
174
+ * operationId: create-sso-connection
175
+ * tags: [Connections]
103
176
  * produces:
104
- * - application/json
177
+ * - application/json
105
178
  * consumes:
106
- * - application/x-www-form-urlencoded
179
+ * - application/x-www-form-urlencoded
180
+ * - application/json
107
181
  * parameters:
108
- * - name: name
109
- * description: Name/identifier for the config
110
- * type: string
111
- * in: formData
112
- * - name: description
113
- * description: A short description for the config not more than 100 characters
114
- * type: string
115
- * in: formData
116
- * - name: encodedRawMetadata
117
- * description: Base64 encoding of the XML metadata
118
- * in: formData
119
- * type: string
120
- * - name: rawMetadata
121
- * description: Raw XML metadata
122
- * in: formData
123
- * type: string
124
- * - name: defaultRedirectUrl
125
- * description: The redirect URL to use in the IdP login flow
126
- * in: formData
127
- * required: true
128
- * type: string
129
- * - name: redirectUrl
130
- * description: JSON encoded array containing a list of allowed redirect URLs
131
- * in: formData
132
- * required: true
133
- * type: string
134
- * - name: tenant
135
- * description: Tenant
136
- * in: formData
137
- * required: true
138
- * type: string
139
- * - name: product
140
- * description: Product
141
- * in: formData
142
- * required: true
143
- * type: string
182
+ * - $ref: '#/parameters/nameParamPost'
183
+ * - $ref: '#/parameters/descriptionParamPost'
184
+ * - $ref: '#/parameters/encodedRawMetadataParamPost'
185
+ * - $ref: '#/parameters/rawMetadataParamPost'
186
+ * - $ref: '#/parameters/defaultRedirectUrlParamPost'
187
+ * - $ref: '#/parameters/redirectUrlParamPost'
188
+ * - $ref: '#/parameters/tenantParamPost'
189
+ * - $ref: '#/parameters/productParamPost'
190
+ * - $ref: '#/parameters/oidcDiscoveryUrlPost'
191
+ * - $ref: '#/parameters/oidcClientIdPost'
192
+ * - $ref: '#/parameters/oidcClientSecretPost'
144
193
  * responses:
145
194
  * 200:
146
195
  * description: Success
147
196
  * schema:
148
- * type: object
149
- * example:
150
- * {
151
- * "idpMetadata": {
152
- * "sso": {
153
- * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
154
- * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
155
- * },
156
- * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
157
- * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
158
- * "loginType": "idp",
159
- * "provider": "okta.com"
160
- * },
161
- * "defaultRedirectUrl": "https://hoppscotch.io/",
162
- * "redirectUrl": ["https://hoppscotch.io/"],
163
- * "tenant": "hoppscotch.io",
164
- * "product": "API Engine",
165
- * "name": "Hoppscotch-SP",
166
- * "description": "SP for hoppscotch.io",
167
- * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
168
- * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
169
- * "certs": {
170
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
171
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
172
- * }
173
- * }
197
+ * $ref: '#/definitions/Connection'
174
198
  * 400:
175
- * 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
199
+ * $ref: '#/definitions/validationErrorsPost'
176
200
  * 401:
177
201
  * description: Unauthorized
178
202
  */
179
- config(body) {
203
+ createSAMLConnection(body) {
180
204
  return __awaiter(this, void 0, void 0, function* () {
181
- const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name, description, } = body;
182
- metrics.increment('createConfig');
183
- this._validateIdPConfig(body);
184
- const redirectUrlList = extractRedirectUrls(redirectUrl);
185
- this._validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
186
- let metaData = rawMetadata;
187
- if (encodedRawMetadata) {
188
- metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
189
- }
190
- const idpMetadata = yield saml20_1.default.parseMetadata(metaData, {});
191
- // extract provider
192
- let providerName = extractHostName(idpMetadata.entityID);
193
- if (!providerName) {
194
- providerName = extractHostName(idpMetadata.sso.redirectUrl || idpMetadata.sso.postUrl);
195
- }
196
- idpMetadata.provider = providerName ? providerName : 'Unknown';
197
- const clientID = dbutils.keyDigest(dbutils.keyFromParts(tenant, product, idpMetadata.entityID));
198
- let clientSecret;
199
- const exists = yield this.configStore.get(clientID);
200
- if (exists) {
201
- clientSecret = exists.clientSecret;
202
- }
203
- else {
204
- clientSecret = crypto_1.default.randomBytes(24).toString('hex');
205
- }
206
- const certs = yield x509_1.default.generate();
207
- if (!certs) {
208
- throw new Error('Error generating x59 certs');
209
- }
210
- const record = {
211
- idpMetadata,
212
- defaultRedirectUrl,
213
- redirectUrl: redirectUrlList,
214
- tenant,
215
- product,
216
- name,
217
- description,
218
- clientID,
219
- clientSecret,
220
- certs,
221
- };
222
- yield this.configStore.put(clientID, record, {
223
- // secondary index on entityID
224
- name: utils_1.IndexNames.EntityID,
225
- value: idpMetadata.entityID,
226
- }, {
227
- // secondary index on tenant + product
228
- name: utils_1.IndexNames.TenantProduct,
229
- value: dbutils.keyFromParts(tenant, product),
230
- });
205
+ metrics.increment('createConnection');
206
+ const record = yield saml_1.default.create(body, this.connectionStore);
207
+ return record;
208
+ });
209
+ }
210
+ // For backwards compatibility
211
+ config(...args) {
212
+ return __awaiter(this, void 0, void 0, function* () {
213
+ return this.createSAMLConnection(...args);
214
+ });
215
+ }
216
+ createOIDCConnection(body) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ metrics.increment('createConnection');
219
+ const record = yield oidc_1.default.create(body, this.connectionStore);
231
220
  return record;
232
221
  });
233
222
  }
234
223
  /**
235
224
  * @swagger
236
- *
225
+ * definitions:
226
+ * validationErrorsPatch:
227
+ * description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters| redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Tenant/Product config mismatch with OIDC Provider metadata
228
+ * parameters:
229
+ * clientIDParamPatch:
230
+ * name: clientID
231
+ * description: Client ID for the connection
232
+ * type: string
233
+ * in: formData
234
+ * required: true
235
+ * clientSecretParamPatch:
236
+ * name: clientSecret
237
+ * description: Client Secret for the connection
238
+ * type: string
239
+ * in: formData
240
+ * required: true
241
+ * nameParamPatch:
242
+ * name: name
243
+ * description: Name/identifier for the connection
244
+ * type: string
245
+ * in: formData
246
+ * descriptionParamPatch:
247
+ * name: description
248
+ * description: A short description for the connection not more than 100 characters
249
+ * type: string
250
+ * in: formData
251
+ * encodedRawMetadataParamPatch:
252
+ * name: encodedRawMetadata
253
+ * description: Base64 encoding of the XML metadata
254
+ * in: formData
255
+ * type: string
256
+ * rawMetadataParamPatch:
257
+ * name: rawMetadata
258
+ * description: Raw XML metadata
259
+ * in: formData
260
+ * type: string
261
+ * oidcDiscoveryUrlPatch:
262
+ * name: oidcDiscoveryUrl
263
+ * description: well-known URL where the OpenID Provider configuration is exposed
264
+ * in: formData
265
+ * type: string
266
+ * oidcClientIdPatch:
267
+ * name: oidcClientId
268
+ * description: clientId of the application set up on the OpenID Provider
269
+ * in: formData
270
+ * type: string
271
+ * oidcClientSecretPatch:
272
+ * name: oidcClientSecret
273
+ * description: clientSecret of the application set up on the OpenID Provider
274
+ * in: formData
275
+ * type: string
276
+ * defaultRedirectUrlParamPatch:
277
+ * name: defaultRedirectUrl
278
+ * description: The redirect URL to use in the IdP login flow
279
+ * in: formData
280
+ * type: string
281
+ * redirectUrlParamPatch:
282
+ * name: redirectUrl
283
+ * description: JSON encoded array containing a list of allowed redirect URLs
284
+ * in: formData
285
+ * type: string
286
+ * tenantParamPatch:
287
+ * name: tenant
288
+ * description: Tenant
289
+ * in: formData
290
+ * required: true
291
+ * type: string
292
+ * productParamPatch:
293
+ * name: product
294
+ * description: Product
295
+ * in: formData
296
+ * required: true
297
+ * type: string
237
298
  * /api/v1/saml/config:
238
299
  * patch:
239
- * summary: Update SAML configuration
300
+ * summary: Update SAML Config
240
301
  * operationId: update-saml-config
241
- * tags: [SAML Config]
302
+ * tags: [SAML Config - Deprecated]
303
+ * deprecated: true
304
+ * consumes:
305
+ * - application/json
306
+ * - application/x-www-form-urlencoded
307
+ * parameters:
308
+ * - $ref: '#/parameters/clientIDParamPatch'
309
+ * - $ref: '#/parameters/clientSecretParamPatch'
310
+ * - $ref: '#/parameters/nameParamPatch'
311
+ * - $ref: '#/parameters/descriptionParamPatch'
312
+ * - $ref: '#/parameters/encodedRawMetadataParamPatch'
313
+ * - $ref: '#/parameters/rawMetadataParamPatch'
314
+ * - $ref: '#/parameters/defaultRedirectUrlParamPatch'
315
+ * - $ref: '#/parameters/redirectUrlParamPatch'
316
+ * - $ref: '#/parameters/tenantParamPatch'
317
+ * - $ref: '#/parameters/productParamPatch'
318
+ * responses:
319
+ * 204:
320
+ * description: Success
321
+ * 400:
322
+ * $ref: '#/definitions/validationErrorsPatch'
323
+ * 401:
324
+ * description: Unauthorized
325
+ * /api/v1/connections:
326
+ * patch:
327
+ * summary: Update SSO Connection
328
+ * operationId: update-sso-connection
329
+ * tags: [Connections]
242
330
  * consumes:
243
331
  * - application/json
244
332
  * - application/x-www-form-urlencoded
245
333
  * parameters:
246
- * - name: clientID
247
- * description: Client ID for the config
248
- * type: string
249
- * in: formData
250
- * required: true
251
- * - name: clientSecret
252
- * description: Client Secret for the config
253
- * type: string
254
- * in: formData
255
- * required: true
256
- * - name: name
257
- * description: Name/identifier for the config
258
- * type: string
259
- * in: formData
260
- * - name: description
261
- * description: A short description for the config not more than 100 characters
262
- * type: string
263
- * in: formData
264
- * - name: encodedRawMetadata
265
- * description: Base64 encoding of the XML metadata
266
- * in: formData
267
- * type: string
268
- * - name: rawMetadata
269
- * description: Raw XML metadata
270
- * in: formData
271
- * type: string
272
- * - name: defaultRedirectUrl
273
- * description: The redirect URL to use in the IdP login flow
274
- * in: formData
275
- * required: true
276
- * type: string
277
- * - name: redirectUrl
278
- * description: JSON encoded array containing a list of allowed redirect URLs
279
- * in: formData
280
- * required: true
281
- * type: string
282
- * - name: tenant
283
- * description: Tenant
284
- * in: formData
285
- * required: true
286
- * type: string
287
- * - name: product
288
- * description: Product
289
- * in: formData
290
- * required: true
291
- * type: string
334
+ * - $ref: '#/parameters/clientIDParamPatch'
335
+ * - $ref: '#/parameters/clientSecretParamPatch'
336
+ * - $ref: '#/parameters/nameParamPatch'
337
+ * - $ref: '#/parameters/descriptionParamPatch'
338
+ * - $ref: '#/parameters/encodedRawMetadataParamPatch'
339
+ * - $ref: '#/parameters/rawMetadataParamPatch'
340
+ * - $ref: '#/parameters/oidcDiscoveryUrlPatch'
341
+ * - $ref: '#/parameters/oidcClientIdPatch'
342
+ * - $ref: '#/parameters/oidcClientSecretPatch'
343
+ * - $ref: '#/parameters/defaultRedirectUrlParamPatch'
344
+ * - $ref: '#/parameters/redirectUrlParamPatch'
345
+ * - $ref: '#/parameters/tenantParamPatch'
346
+ * - $ref: '#/parameters/productParamPatch'
292
347
  * responses:
293
348
  * 204:
294
349
  * description: Success
295
350
  * 400:
296
- * description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters
351
+ * $ref: '#/definitions/validationErrorsPatch'
297
352
  * 401:
298
353
  * description: Unauthorized
299
354
  */
300
- updateConfig(body) {
355
+ updateSAMLConnection(body) {
301
356
  return __awaiter(this, void 0, void 0, function* () {
302
- const { encodedRawMetadata, // could be empty
303
- rawMetadata, // could be empty
304
- defaultRedirectUrl, redirectUrl, name, description } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description"]);
305
- if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
306
- throw new error_1.JacksonError('Please provide clientID', 400);
307
- }
308
- if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
309
- throw new error_1.JacksonError('Please provide clientSecret', 400);
310
- }
311
- if (description && description.length > 100) {
312
- throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
313
- }
314
- const redirectUrlList = redirectUrl ? extractRedirectUrls(redirectUrl) : null;
315
- this._validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
316
- const _currentConfig = yield this.getConfig(clientInfo);
317
- if (_currentConfig.clientSecret !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
318
- throw new error_1.JacksonError('clientSecret mismatch', 400);
319
- }
320
- let metaData = rawMetadata;
321
- if (encodedRawMetadata) {
322
- metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
323
- }
324
- let newMetadata;
325
- if (metaData) {
326
- newMetadata = yield saml20_1.default.parseMetadata(metaData, {});
327
- // extract provider
328
- let providerName = extractHostName(newMetadata.entityID);
329
- if (!providerName) {
330
- providerName = extractHostName(newMetadata.sso.redirectUrl || newMetadata.sso.postUrl);
357
+ yield saml_1.default.update(body, this.connectionStore, this.getConnections.bind(this));
358
+ });
359
+ }
360
+ // For backwards compatibility
361
+ updateConfig(...args) {
362
+ return __awaiter(this, void 0, void 0, function* () {
363
+ yield this.updateSAMLConnection(...args);
364
+ });
365
+ }
366
+ updateOIDCConnection(body) {
367
+ return __awaiter(this, void 0, void 0, function* () {
368
+ yield oidc_1.default.update(body, this.connectionStore, this.getConnections.bind(this));
369
+ });
370
+ }
371
+ /**
372
+ * @swagger
373
+ * parameters:
374
+ * tenantParamGet:
375
+ * in: query
376
+ * name: tenant
377
+ * type: string
378
+ * description: Tenant
379
+ * productParamGet:
380
+ * in: query
381
+ * name: product
382
+ * type: string
383
+ * description: Product
384
+ * clientIDParamGet:
385
+ * in: query
386
+ * name: clientID
387
+ * type: string
388
+ * description: Client ID
389
+ * definitions:
390
+ * Connection:
391
+ * type: object
392
+ * properties:
393
+ * clientID:
394
+ * type: string
395
+ * description: Connection clientID
396
+ * clientSecret:
397
+ * type: string
398
+ * description: Connection clientSecret
399
+ * name:
400
+ * type: string
401
+ * description: Connection name
402
+ * description:
403
+ * type: string
404
+ * description: Connection description
405
+ * redirectUrl:
406
+ * type: string
407
+ * description: A list of allowed redirect URLs
408
+ * defaultRedirectUrl:
409
+ * type: string
410
+ * description: The redirect URL to use in the IdP login flow
411
+ * tenant:
412
+ * type: string
413
+ * description: Connection tenant
414
+ * product:
415
+ * type: string
416
+ * description: Connection product
417
+ * idpMetadata:
418
+ * type: object
419
+ * description: SAML IdP metadata
420
+ * certs:
421
+ * type: object
422
+ * description: Certs generated for SAML connection
423
+ * oidcProvider:
424
+ * type: object
425
+ * description: OIDC IdP metadata
426
+ * responses:
427
+ * '200Get':
428
+ * description: Success
429
+ * schema:
430
+ * type: array
431
+ * items:
432
+ * $ref: '#/definitions/Connection'
433
+ * '400Get':
434
+ * description: Please provide `clientID` or `tenant` and `product`.
435
+ * '401Get':
436
+ * description: Unauthorized
437
+ * /api/v1/connections:
438
+ * get:
439
+ * summary: Get SSO Connections
440
+ * parameters:
441
+ * - $ref: '#/parameters/tenantParamGet'
442
+ * - $ref: '#/parameters/productParamGet'
443
+ * - $ref: '#/parameters/clientIDParamGet'
444
+ * operationId: get-connections
445
+ * tags: [Connections]
446
+ * responses:
447
+ * '200':
448
+ * $ref: '#/responses/200Get'
449
+ * '400':
450
+ * $ref: '#/responses/400Get'
451
+ * '401':
452
+ * $ref: '#/responses/401Get'
453
+ */
454
+ getConnections(body) {
455
+ return __awaiter(this, void 0, void 0, function* () {
456
+ const clientID = 'clientID' in body ? body.clientID : undefined;
457
+ const tenant = 'tenant' in body ? body.tenant : undefined;
458
+ const product = 'product' in body ? body.product : undefined;
459
+ const strategy = 'strategy' in body ? body.strategy : undefined;
460
+ metrics.increment('getConnections');
461
+ if (clientID) {
462
+ const connection = yield this.connectionStore.get(clientID);
463
+ if (!connection || typeof connection !== 'object') {
464
+ return [];
331
465
  }
332
- newMetadata.provider = providerName ? providerName : 'Unknown';
466
+ return [connection];
333
467
  }
334
- if (newMetadata) {
335
- // check if clientID matches with new metadata payload
336
- const clientID = dbutils.keyDigest(dbutils.keyFromParts(clientInfo.tenant, clientInfo.product, newMetadata.entityID));
337
- if (clientID !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
338
- throw new error_1.JacksonError('Tenant/Product config mismatch with IdP metadata', 400);
468
+ if (tenant && product) {
469
+ const connections = yield this.connectionStore.getByIndex({
470
+ name: utils_1.IndexNames.TenantProduct,
471
+ value: dbutils.keyFromParts(tenant, product),
472
+ });
473
+ if (!connections || !connections.length) {
474
+ return [];
475
+ }
476
+ // filter if strategy is passed
477
+ const filteredConnections = strategy
478
+ ? connections.filter((connection) => {
479
+ if (strategy === 'saml') {
480
+ if (connection.idpMetadata) {
481
+ return true;
482
+ }
483
+ }
484
+ if (strategy === 'oidc') {
485
+ if (connection.oidcProvider) {
486
+ return true;
487
+ }
488
+ }
489
+ return false;
490
+ })
491
+ : connections;
492
+ if (!filteredConnections.length) {
493
+ return [];
339
494
  }
495
+ return filteredConnections;
340
496
  }
341
- const record = 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: redirectUrlList ? redirectUrlList : _currentConfig.redirectUrl });
342
- yield this.configStore.put(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID, record, {
343
- // secondary index on entityID
344
- name: utils_1.IndexNames.EntityID,
345
- value: _currentConfig.idpMetadata.entityID,
346
- }, {
347
- // secondary index on tenant + product
348
- name: utils_1.IndexNames.TenantProduct,
349
- value: dbutils.keyFromParts(_currentConfig.tenant, _currentConfig.product),
350
- });
497
+ throw new error_1.JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
351
498
  });
352
499
  }
353
500
  /**
354
501
  * @swagger
355
- *
356
502
  * /api/v1/saml/config:
357
503
  * get:
358
- * summary: Get SAML configuration
504
+ * summary: Get SAML Config
359
505
  * operationId: get-saml-config
360
- * tags:
361
- * - SAML Config
506
+ * tags: [SAML Config - Deprecated]
507
+ * deprecated: true
362
508
  * parameters:
363
- * - in: query
364
- * name: tenant
365
- * type: string
366
- * description: Tenant
367
- * - in: query
368
- * name: product
369
- * type: string
370
- * description: Product
371
- * - in: query
372
- * name: clientID
373
- * type: string
374
- * description: Client ID
509
+ * - $ref: '#/parameters/tenantParamGet'
510
+ * - $ref: '#/parameters/productParamGet'
511
+ * - $ref: '#/parameters/clientIDParamGet'
375
512
  * responses:
376
- * '200':
513
+ * '200':
377
514
  * description: Success
378
515
  * schema:
379
516
  * type: object
380
517
  * example:
381
518
  * {
382
- * "idpMetadata": {
383
- * "sso": {
384
- * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
385
- * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
386
- * },
387
- * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
388
- * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
389
- * "loginType": "idp",
390
- * "provider": "okta.com"
391
- * },
392
- * "defaultRedirectUrl": "https://hoppscotch.io/",
393
- * "redirectUrl": ["https://hoppscotch.io/"],
394
- * "tenant": "hoppscotch.io",
395
- * "product": "API Engine",
396
- * "name": "Hoppscotch-SP",
397
- * "description": "SP for hoppscotch.io",
398
- * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
399
- * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
400
- * "certs": {
401
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
402
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
403
- * }
404
- * }
405
- * '400':
406
- * description: Please provide `clientID` or `tenant` and `product`.
407
- * '401':
408
- * description: Unauthorized
519
+ * "idpMetadata": {
520
+ * "sso": {
521
+ * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
522
+ * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
523
+ * },
524
+ * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
525
+ * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
526
+ * "loginType": "idp",
527
+ * "provider": "okta.com"
528
+ * },
529
+ * "defaultRedirectUrl": "https://hoppscotch.io/",
530
+ * "redirectUrl": ["https://hoppscotch.io/"],
531
+ * "tenant": "hoppscotch.io",
532
+ * "product": "API Engine",
533
+ * "name": "Hoppscotch-SP",
534
+ * "description": "SP for hoppscotch.io",
535
+ * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
536
+ * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
537
+ * "certs": {
538
+ * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
539
+ * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
540
+ * }
541
+ * }
542
+ * '400':
543
+ * $ref: '#/responses/400Get'
544
+ * '401':
545
+ * $ref: '#/responses/401Get'
409
546
  */
410
547
  getConfig(body) {
411
548
  return __awaiter(this, void 0, void 0, function* () {
412
- const { clientID, tenant, product } = body;
413
- metrics.increment('getConfig');
549
+ const clientID = 'clientID' in body ? body.clientID : undefined;
550
+ const tenant = 'tenant' in body ? body.tenant : undefined;
551
+ const product = 'product' in body ? body.product : undefined;
552
+ metrics.increment('getConnections');
414
553
  if (clientID) {
415
- const samlConfig = yield this.configStore.get(clientID);
554
+ const samlConfig = yield this.connectionStore.get(clientID);
416
555
  return samlConfig || {};
417
556
  }
418
557
  if (tenant && product) {
419
- const samlConfigs = yield this.configStore.getByIndex({
558
+ const samlConfigs = yield this.connectionStore.getByIndex({
420
559
  name: utils_1.IndexNames.TenantProduct,
421
560
  value: dbutils.keyFromParts(tenant, product),
422
561
  });
@@ -430,52 +569,90 @@ class APIController {
430
569
  }
431
570
  /**
432
571
  * @swagger
572
+ * parameters:
573
+ * clientIDDel:
574
+ * name: clientID
575
+ * in: formData
576
+ * type: string
577
+ * description: Client ID
578
+ * clientSecretDel:
579
+ * name: clientSecret
580
+ * in: formData
581
+ * type: string
582
+ * description: Client Secret
583
+ * tenantDel:
584
+ * name: tenant
585
+ * in: formData
586
+ * type: string
587
+ * description: Tenant
588
+ * productDel:
589
+ * name: product
590
+ * in: formData
591
+ * type: string
592
+ * description: Product
593
+ * strategyDel:
594
+ * name: strategy
595
+ * in: formData
596
+ * type: string
597
+ * description: Strategy
598
+ * /api/v1/connections:
599
+ * delete:
600
+ * parameters:
601
+ * - $ref: '#/parameters/clientIDDel'
602
+ * - $ref: '#/parameters/clientSecretDel'
603
+ * - $ref: '#/parameters/tenantDel'
604
+ * - $ref: '#/parameters/productDel'
605
+ * - $ref: '#/parameters/strategyDel'
606
+ * summary: Delete SSO Connections
607
+ * operationId: delete-sso-connection
608
+ * tags: [Connections]
609
+ * consumes:
610
+ * - application/x-www-form-urlencoded
611
+ * - application/json
612
+ * responses:
613
+ * '200':
614
+ * description: Success
615
+ * '400':
616
+ * description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.
617
+ * '401':
618
+ * description: Unauthorized
433
619
  * /api/v1/saml/config:
434
620
  * delete:
435
- * summary: Delete SAML configuration
621
+ * summary: Delete SAML Config
436
622
  * operationId: delete-saml-config
437
- * tags:
438
- * - SAML Config
623
+ * tags: [SAML Config - Deprecated]
624
+ * deprecated: true
439
625
  * consumes:
440
626
  * - application/x-www-form-urlencoded
627
+ * - application/json
441
628
  * parameters:
442
- * - name: clientID
443
- * in: formData
444
- * type: string
445
- * required: true
446
- * description: Client ID
447
- * - name: clientSecret
448
- * in: formData
449
- * type: string
450
- * required: true
451
- * description: Client Secret
452
- * - name: tenant
453
- * in: formData
454
- * type: string
455
- * description: Tenant
456
- * - name: product
457
- * in: formData
458
- * type: string
459
- * description: Product
629
+ * - $ref: '#/parameters/clientIDDel'
630
+ * - $ref: '#/parameters/clientSecretDel'
631
+ * - $ref: '#/parameters/tenantDel'
632
+ * - $ref: '#/parameters/productDel'
460
633
  * responses:
461
634
  * '200':
462
635
  * description: Success
463
636
  * '400':
464
- * description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.'
637
+ * description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.
465
638
  * '401':
466
639
  * description: Unauthorized
467
640
  */
468
- deleteConfig(body) {
641
+ deleteConnections(body) {
469
642
  return __awaiter(this, void 0, void 0, function* () {
470
- const { clientID, clientSecret, tenant, product } = body;
471
- metrics.increment('deleteConfig');
643
+ const clientID = 'clientID' in body ? body.clientID : undefined;
644
+ const clientSecret = 'clientSecret' in body ? body.clientSecret : undefined;
645
+ const tenant = 'tenant' in body ? body.tenant : undefined;
646
+ const product = 'product' in body ? body.product : undefined;
647
+ const strategy = 'strategy' in body ? body.strategy : undefined;
648
+ metrics.increment('deleteConnections');
472
649
  if (clientID && clientSecret) {
473
- const samlConfig = yield this.configStore.get(clientID);
474
- if (!samlConfig) {
650
+ const connection = yield this.connectionStore.get(clientID);
651
+ if (!connection) {
475
652
  return;
476
653
  }
477
- if (samlConfig.clientSecret === clientSecret) {
478
- yield this.configStore.delete(clientID);
654
+ if (connection.clientSecret === clientSecret) {
655
+ yield this.connectionStore.delete(clientID);
479
656
  }
480
657
  else {
481
658
  throw new error_1.JacksonError('clientSecret mismatch', 400);
@@ -483,47 +660,41 @@ class APIController {
483
660
  return;
484
661
  }
485
662
  if (tenant && product) {
486
- const samlConfigs = yield this.configStore.getByIndex({
663
+ const connections = yield this.connectionStore.getByIndex({
487
664
  name: utils_1.IndexNames.TenantProduct,
488
665
  value: dbutils.keyFromParts(tenant, product),
489
666
  });
490
- if (!samlConfigs || !samlConfigs.length) {
667
+ if (!connections || !connections.length) {
491
668
  return;
492
669
  }
493
- for (const conf of samlConfigs) {
494
- yield this.configStore.delete(conf.clientID);
670
+ // filter if strategy is passed
671
+ const filteredConnections = strategy
672
+ ? connections.filter((connection) => {
673
+ if (strategy === 'saml') {
674
+ if (connection.idpMetadata) {
675
+ return true;
676
+ }
677
+ }
678
+ if (strategy === 'oidc') {
679
+ if (connection.oidcProvider) {
680
+ return true;
681
+ }
682
+ }
683
+ return false;
684
+ })
685
+ : connections;
686
+ for (const conf of filteredConnections) {
687
+ yield this.connectionStore.delete(conf.clientID);
495
688
  }
496
689
  return;
497
690
  }
498
691
  throw new error_1.JacksonError('Please provide `clientID` and `clientSecret` or `tenant` and `product`.', 400);
499
692
  });
500
693
  }
501
- }
502
- exports.APIController = APIController;
503
- const extractHostName = (url) => {
504
- try {
505
- const pUrl = new URL(url);
506
- if (pUrl.hostname.startsWith('www.')) {
507
- return pUrl.hostname.substring(4);
508
- }
509
- return pUrl.hostname;
510
- }
511
- catch (err) {
512
- return null;
513
- }
514
- };
515
- const extractRedirectUrls = (urls) => {
516
- if (!urls) {
517
- return [];
518
- }
519
- if (typeof urls === 'string') {
520
- if (urls.startsWith('[')) {
521
- // redirectUrl is a stringified array
522
- return JSON.parse(urls);
523
- }
524
- // redirectUrl is a single URL
525
- return [urls];
694
+ deleteConfig(body) {
695
+ return __awaiter(this, void 0, void 0, function* () {
696
+ yield this.deleteConnections(Object.assign(Object.assign({}, body), { strategy: 'saml' }));
697
+ });
526
698
  }
527
- // redirectUrl is an array of URLs
528
- return urls;
529
- };
699
+ }
700
+ exports.ConnectionAPIController = ConnectionAPIController;