@boxyhq/saml-jackson 1.2.2 → 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,394 +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
- const forceAuthn = body.forceAuthn == 'true' || body.forceAuthn == true;
183
- metrics.increment('createConfig');
184
- this._validateIdPConfig(body);
185
- const redirectUrlList = extractRedirectUrls(redirectUrl);
186
- this._validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
187
- let metaData = rawMetadata;
188
- if (encodedRawMetadata) {
189
- metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
190
- }
191
- const idpMetadata = yield saml20_1.default.parseMetadata(metaData, {});
192
- // extract provider
193
- let providerName = extractHostName(idpMetadata.entityID);
194
- if (!providerName) {
195
- providerName = extractHostName(idpMetadata.sso.redirectUrl || idpMetadata.sso.postUrl);
196
- }
197
- idpMetadata.provider = providerName ? providerName : 'Unknown';
198
- const clientID = dbutils.keyDigest(dbutils.keyFromParts(tenant, product, idpMetadata.entityID));
199
- let clientSecret;
200
- const exists = yield this.configStore.get(clientID);
201
- if (exists) {
202
- clientSecret = exists.clientSecret;
203
- }
204
- else {
205
- clientSecret = crypto_1.default.randomBytes(24).toString('hex');
206
- }
207
- const certs = yield x509_1.default.generate();
208
- if (!certs) {
209
- throw new Error('Error generating x509 certs');
210
- }
211
- const record = {
212
- idpMetadata,
213
- defaultRedirectUrl,
214
- redirectUrl: redirectUrlList,
215
- tenant,
216
- product,
217
- name,
218
- description,
219
- clientID,
220
- clientSecret,
221
- certs,
222
- forceAuthn,
223
- };
224
- yield this.configStore.put(clientID, record, {
225
- // secondary index on entityID
226
- name: utils_1.IndexNames.EntityID,
227
- value: idpMetadata.entityID,
228
- }, {
229
- // secondary index on tenant + product
230
- name: utils_1.IndexNames.TenantProduct,
231
- value: dbutils.keyFromParts(tenant, product),
232
- });
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);
233
220
  return record;
234
221
  });
235
222
  }
236
223
  /**
237
224
  * @swagger
238
- *
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
239
298
  * /api/v1/saml/config:
240
299
  * patch:
241
- * summary: Update SAML configuration
300
+ * summary: Update SAML Config
242
301
  * operationId: update-saml-config
243
- * 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]
244
330
  * consumes:
245
331
  * - application/json
246
332
  * - application/x-www-form-urlencoded
247
333
  * parameters:
248
- * - name: clientID
249
- * description: Client ID for the config
250
- * type: string
251
- * in: formData
252
- * required: true
253
- * - name: clientSecret
254
- * description: Client Secret for the config
255
- * type: string
256
- * in: formData
257
- * required: true
258
- * - name: name
259
- * description: Name/identifier for the config
260
- * type: string
261
- * in: formData
262
- * - name: description
263
- * description: A short description for the config not more than 100 characters
264
- * type: string
265
- * in: formData
266
- * - name: encodedRawMetadata
267
- * description: Base64 encoding of the XML metadata
268
- * in: formData
269
- * type: string
270
- * - name: rawMetadata
271
- * description: Raw XML metadata
272
- * in: formData
273
- * type: string
274
- * - name: defaultRedirectUrl
275
- * description: The redirect URL to use in the IdP login flow
276
- * in: formData
277
- * required: true
278
- * type: string
279
- * - name: redirectUrl
280
- * description: JSON encoded array containing a list of allowed redirect URLs
281
- * in: formData
282
- * required: true
283
- * type: string
284
- * - name: tenant
285
- * description: Tenant
286
- * in: formData
287
- * required: true
288
- * type: string
289
- * - name: product
290
- * description: Product
291
- * in: formData
292
- * required: true
293
- * 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'
294
347
  * responses:
295
348
  * 204:
296
349
  * description: Success
297
350
  * 400:
298
- * 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'
299
352
  * 401:
300
353
  * description: Unauthorized
301
354
  */
302
- updateConfig(body) {
355
+ updateSAMLConnection(body) {
303
356
  return __awaiter(this, void 0, void 0, function* () {
304
- const { encodedRawMetadata, // could be empty
305
- rawMetadata, // could be empty
306
- defaultRedirectUrl, redirectUrl, name, description, forceAuthn = false } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description", "forceAuthn"]);
307
- if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
308
- throw new error_1.JacksonError('Please provide clientID', 400);
309
- }
310
- if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
311
- throw new error_1.JacksonError('Please provide clientSecret', 400);
312
- }
313
- if (description && description.length > 100) {
314
- throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
315
- }
316
- const redirectUrlList = redirectUrl ? extractRedirectUrls(redirectUrl) : null;
317
- this._validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
318
- const _currentConfig = yield this.getConfig(clientInfo);
319
- if (_currentConfig.clientSecret !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
320
- throw new error_1.JacksonError('clientSecret mismatch', 400);
321
- }
322
- let metaData = rawMetadata;
323
- if (encodedRawMetadata) {
324
- metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
325
- }
326
- let newMetadata;
327
- if (metaData) {
328
- newMetadata = yield saml20_1.default.parseMetadata(metaData, {});
329
- // extract provider
330
- let providerName = extractHostName(newMetadata.entityID);
331
- if (!providerName) {
332
- 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 [];
333
465
  }
334
- newMetadata.provider = providerName ? providerName : 'Unknown';
466
+ return [connection];
335
467
  }
336
- if (newMetadata) {
337
- // check if clientID matches with new metadata payload
338
- const clientID = dbutils.keyDigest(dbutils.keyFromParts(clientInfo.tenant, clientInfo.product, newMetadata.entityID));
339
- if (clientID !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
340
- 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 [];
341
494
  }
495
+ return filteredConnections;
342
496
  }
343
- 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, forceAuthn });
344
- yield this.configStore.put(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID, record, {
345
- // secondary index on entityID
346
- name: utils_1.IndexNames.EntityID,
347
- value: _currentConfig.idpMetadata.entityID,
348
- }, {
349
- // secondary index on tenant + product
350
- name: utils_1.IndexNames.TenantProduct,
351
- value: dbutils.keyFromParts(_currentConfig.tenant, _currentConfig.product),
352
- });
497
+ throw new error_1.JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
353
498
  });
354
499
  }
355
500
  /**
356
501
  * @swagger
357
- *
358
502
  * /api/v1/saml/config:
359
503
  * get:
360
- * summary: Get SAML configuration
504
+ * summary: Get SAML Config
361
505
  * operationId: get-saml-config
362
- * tags:
363
- * - SAML Config
506
+ * tags: [SAML Config - Deprecated]
507
+ * deprecated: true
364
508
  * parameters:
365
- * - in: query
366
- * name: tenant
367
- * type: string
368
- * description: Tenant
369
- * - in: query
370
- * name: product
371
- * type: string
372
- * description: Product
373
- * - in: query
374
- * name: clientID
375
- * type: string
376
- * description: Client ID
509
+ * - $ref: '#/parameters/tenantParamGet'
510
+ * - $ref: '#/parameters/productParamGet'
511
+ * - $ref: '#/parameters/clientIDParamGet'
377
512
  * responses:
378
- * '200':
513
+ * '200':
379
514
  * description: Success
380
515
  * schema:
381
516
  * type: object
382
517
  * example:
383
518
  * {
384
- * "idpMetadata": {
385
- * "sso": {
386
- * "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
387
- * "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
388
- * },
389
- * "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
390
- * "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
391
- * "loginType": "idp",
392
- * "provider": "okta.com"
393
- * },
394
- * "defaultRedirectUrl": "https://hoppscotch.io/",
395
- * "redirectUrl": ["https://hoppscotch.io/"],
396
- * "tenant": "hoppscotch.io",
397
- * "product": "API Engine",
398
- * "name": "Hoppscotch-SP",
399
- * "description": "SP for hoppscotch.io",
400
- * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
401
- * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
402
- * "certs": {
403
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
404
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
405
- * }
406
- * }
407
- * '400':
408
- * description: Please provide `clientID` or `tenant` and `product`.
409
- * '401':
410
- * 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'
411
546
  */
412
547
  getConfig(body) {
413
548
  return __awaiter(this, void 0, void 0, function* () {
414
- const { clientID, tenant, product } = body;
415
- 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');
416
553
  if (clientID) {
417
- const samlConfig = yield this.configStore.get(clientID);
554
+ const samlConfig = yield this.connectionStore.get(clientID);
418
555
  return samlConfig || {};
419
556
  }
420
557
  if (tenant && product) {
421
- const samlConfigs = yield this.configStore.getByIndex({
558
+ const samlConfigs = yield this.connectionStore.getByIndex({
422
559
  name: utils_1.IndexNames.TenantProduct,
423
560
  value: dbutils.keyFromParts(tenant, product),
424
561
  });
@@ -432,52 +569,90 @@ class APIController {
432
569
  }
433
570
  /**
434
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
435
619
  * /api/v1/saml/config:
436
620
  * delete:
437
- * summary: Delete SAML configuration
621
+ * summary: Delete SAML Config
438
622
  * operationId: delete-saml-config
439
- * tags:
440
- * - SAML Config
623
+ * tags: [SAML Config - Deprecated]
624
+ * deprecated: true
441
625
  * consumes:
442
626
  * - application/x-www-form-urlencoded
627
+ * - application/json
443
628
  * parameters:
444
- * - name: clientID
445
- * in: formData
446
- * type: string
447
- * required: true
448
- * description: Client ID
449
- * - name: clientSecret
450
- * in: formData
451
- * type: string
452
- * required: true
453
- * description: Client Secret
454
- * - name: tenant
455
- * in: formData
456
- * type: string
457
- * description: Tenant
458
- * - name: product
459
- * in: formData
460
- * type: string
461
- * description: Product
629
+ * - $ref: '#/parameters/clientIDDel'
630
+ * - $ref: '#/parameters/clientSecretDel'
631
+ * - $ref: '#/parameters/tenantDel'
632
+ * - $ref: '#/parameters/productDel'
462
633
  * responses:
463
634
  * '200':
464
635
  * description: Success
465
636
  * '400':
466
- * 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`.
467
638
  * '401':
468
639
  * description: Unauthorized
469
640
  */
470
- deleteConfig(body) {
641
+ deleteConnections(body) {
471
642
  return __awaiter(this, void 0, void 0, function* () {
472
- const { clientID, clientSecret, tenant, product } = body;
473
- 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');
474
649
  if (clientID && clientSecret) {
475
- const samlConfig = yield this.configStore.get(clientID);
476
- if (!samlConfig) {
650
+ const connection = yield this.connectionStore.get(clientID);
651
+ if (!connection) {
477
652
  return;
478
653
  }
479
- if (samlConfig.clientSecret === clientSecret) {
480
- yield this.configStore.delete(clientID);
654
+ if (connection.clientSecret === clientSecret) {
655
+ yield this.connectionStore.delete(clientID);
481
656
  }
482
657
  else {
483
658
  throw new error_1.JacksonError('clientSecret mismatch', 400);
@@ -485,47 +660,41 @@ class APIController {
485
660
  return;
486
661
  }
487
662
  if (tenant && product) {
488
- const samlConfigs = yield this.configStore.getByIndex({
663
+ const connections = yield this.connectionStore.getByIndex({
489
664
  name: utils_1.IndexNames.TenantProduct,
490
665
  value: dbutils.keyFromParts(tenant, product),
491
666
  });
492
- if (!samlConfigs || !samlConfigs.length) {
667
+ if (!connections || !connections.length) {
493
668
  return;
494
669
  }
495
- for (const conf of samlConfigs) {
496
- 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);
497
688
  }
498
689
  return;
499
690
  }
500
691
  throw new error_1.JacksonError('Please provide `clientID` and `clientSecret` or `tenant` and `product`.', 400);
501
692
  });
502
693
  }
503
- }
504
- exports.APIController = APIController;
505
- const extractHostName = (url) => {
506
- try {
507
- const pUrl = new URL(url);
508
- if (pUrl.hostname.startsWith('www.')) {
509
- return pUrl.hostname.substring(4);
510
- }
511
- return pUrl.hostname;
512
- }
513
- catch (err) {
514
- return null;
515
- }
516
- };
517
- const extractRedirectUrls = (urls) => {
518
- if (!urls) {
519
- return [];
520
- }
521
- if (typeof urls === 'string') {
522
- if (urls.startsWith('[')) {
523
- // redirectUrl is a stringified array
524
- return JSON.parse(urls);
525
- }
526
- // redirectUrl is a single URL
527
- 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
+ });
528
698
  }
529
- // redirectUrl is an array of URLs
530
- return urls;
531
- };
699
+ }
700
+ exports.ConnectionAPIController = ConnectionAPIController;