@boxyhq/saml-jackson 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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;