@boxyhq/saml-jackson 1.2.2 → 1.3.1

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