@boxyhq/saml-jackson 1.9.0 → 1.9.2-beta.6087

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.
Files changed (49) hide show
  1. package/dist/controller/admin.d.ts +7 -3
  2. package/dist/controller/admin.js +17 -1
  3. package/dist/controller/admin.js.map +1 -1
  4. package/dist/controller/connection/saml.js +2 -5
  5. package/dist/controller/connection/saml.js.map +1 -1
  6. package/dist/controller/oauth.d.ts +3 -1
  7. package/dist/controller/oauth.js +321 -211
  8. package/dist/controller/oauth.js.map +1 -1
  9. package/dist/controller/saml-handler.d.ts +1 -0
  10. package/dist/controller/saml-handler.js +4 -2
  11. package/dist/controller/saml-handler.js.map +1 -1
  12. package/dist/controller/utils.d.ts +2 -1
  13. package/dist/controller/utils.js +1 -0
  14. package/dist/controller/utils.js.map +1 -1
  15. package/dist/directory-sync/DirectoryUsers.js +15 -14
  16. package/dist/directory-sync/DirectoryUsers.js.map +1 -1
  17. package/dist/directory-sync/types.d.ts +15 -0
  18. package/dist/directory-sync/utils.d.ts +13 -9
  19. package/dist/directory-sync/utils.js +60 -28
  20. package/dist/directory-sync/utils.js.map +1 -1
  21. package/dist/ee/branding/index.d.ts +15 -0
  22. package/dist/ee/branding/index.js +49 -0
  23. package/dist/ee/branding/index.js.map +1 -0
  24. package/dist/ee/federated-saml/app.d.ts +12 -5
  25. package/dist/ee/federated-saml/app.js +19 -12
  26. package/dist/ee/federated-saml/app.js.map +1 -1
  27. package/dist/ee/federated-saml/index.d.ts +3 -2
  28. package/dist/ee/federated-saml/index.js +2 -2
  29. package/dist/ee/federated-saml/index.js.map +1 -1
  30. package/dist/ee/federated-saml/sso.d.ts +4 -1
  31. package/dist/ee/federated-saml/sso.js +70 -45
  32. package/dist/ee/federated-saml/sso.js.map +1 -1
  33. package/dist/ee/federated-saml/types.d.ts +3 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.js +12 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/saml/lib.d.ts +1 -0
  38. package/dist/saml/lib.js +3 -2
  39. package/dist/saml/lib.js.map +1 -1
  40. package/dist/saml-tracer/index.d.ts +14 -0
  41. package/dist/saml-tracer/index.js +87 -0
  42. package/dist/saml-tracer/index.js.map +1 -0
  43. package/dist/saml-tracer/types.d.ts +31 -0
  44. package/dist/saml-tracer/types.js +3 -0
  45. package/dist/saml-tracer/types.js.map +1 -0
  46. package/dist/typings.d.ts +12 -0
  47. package/dist/typings.js +1 -0
  48. package/dist/typings.js.map +1 -1
  49. package/package.json +9 -7
@@ -54,11 +54,12 @@ const lib_1 = require("../saml/lib");
54
54
  const oidc_issuer_1 = require("./oauth/oidc-issuer");
55
55
  const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
56
56
  class OAuthController {
57
- constructor({ connectionStore, sessionStore, codeStore, tokenStore, opts }) {
57
+ constructor({ connectionStore, sessionStore, codeStore, tokenStore, samlTracer, opts }) {
58
58
  this.connectionStore = connectionStore;
59
59
  this.sessionStore = sessionStore;
60
60
  this.codeStore = codeStore;
61
61
  this.tokenStore = tokenStore;
62
+ this.samlTracer = samlTracer;
62
63
  this.opts = opts;
63
64
  this.samlHandler = new saml_handler_1.SAMLHandler({
64
65
  connection: connectionStore,
@@ -70,55 +71,25 @@ class OAuthController {
70
71
  var _a;
71
72
  return __awaiter(this, void 0, void 0, function* () {
72
73
  const { response_type = 'code', client_id, redirect_uri, state, scope, nonce, code_challenge, code_challenge_method = '', idp_hint, forceAuthn = 'false', } = body;
73
- const tenant = 'tenant' in body ? body.tenant : undefined;
74
- const product = 'product' in body ? body.product : undefined;
75
- const access_type = 'access_type' in body ? body.access_type : undefined;
76
- const resource = 'resource' in body ? body.resource : undefined;
77
- let requestedTenant = tenant;
78
- let requestedProduct = product;
79
- metrics.increment('oauthAuthorize');
80
- if (!redirect_uri) {
81
- throw new error_1.JacksonError('Please specify a redirect URL.', 400);
82
- }
74
+ let requestedTenant;
75
+ let requestedProduct;
76
+ let requestedScopes;
77
+ let requestedOIDCFlow;
83
78
  let connection;
84
- const requestedScopes = (0, utils_1.getScopeValues)(scope);
85
- const requestedOIDCFlow = requestedScopes.includes('openid');
86
- if (tenant && product) {
87
- const response = yield this.samlHandler.resolveConnection({
88
- tenant,
89
- product,
90
- idp_hint,
91
- authFlow: 'oauth',
92
- originalParams: Object.assign({}, body),
93
- });
94
- if ('redirectUrl' in response) {
95
- return {
96
- redirect_url: response.redirectUrl,
97
- };
98
- }
99
- if ('connection' in response) {
100
- connection = response.connection;
101
- }
102
- }
103
- else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
104
- // if tenant and product are encoded in the client_id then we parse it and check for the relevant connection(s)
105
- let sp = (0, utils_1.getEncodedTenantProduct)(client_id);
106
- if (!sp && access_type) {
107
- sp = (0, utils_1.getEncodedTenantProduct)(access_type);
108
- }
109
- if (!sp && resource) {
110
- sp = (0, utils_1.getEncodedTenantProduct)(resource);
111
- }
112
- if (!sp && requestedScopes) {
113
- const encodedParams = requestedScopes.find((scope) => scope.includes('=') && scope.includes('&')); // for now assume only one encoded param i.e. for tenant/product
114
- if (encodedParams) {
115
- sp = (0, utils_1.getEncodedTenantProduct)(encodedParams);
116
- }
117
- }
118
- if (sp && sp.tenant && sp.product) {
119
- const { tenant, product } = sp;
120
- requestedTenant = tenant;
121
- requestedProduct = product;
79
+ try {
80
+ const tenant = 'tenant' in body ? body.tenant : undefined;
81
+ const product = 'product' in body ? body.product : undefined;
82
+ const access_type = 'access_type' in body ? body.access_type : undefined;
83
+ const resource = 'resource' in body ? body.resource : undefined;
84
+ requestedTenant = tenant;
85
+ requestedProduct = product;
86
+ metrics.increment('oauthAuthorize');
87
+ if (!redirect_uri) {
88
+ throw new error_1.JacksonError('Please specify a redirect URL.', 400);
89
+ }
90
+ requestedScopes = (0, utils_1.getScopeValues)(scope);
91
+ requestedOIDCFlow = requestedScopes.includes('openid');
92
+ if (tenant && product) {
122
93
  const response = yield this.samlHandler.resolveConnection({
123
94
  tenant,
124
95
  product,
@@ -135,47 +106,113 @@ class OAuthController {
135
106
  connection = response.connection;
136
107
  }
137
108
  }
138
- else {
139
- connection = yield this.connectionStore.get(client_id);
140
- if (connection) {
141
- requestedTenant = connection.tenant;
142
- requestedProduct = connection.product;
109
+ else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
110
+ // if tenant and product are encoded in the client_id then we parse it and check for the relevant connection(s)
111
+ let sp = (0, utils_1.getEncodedTenantProduct)(client_id);
112
+ if (!sp && access_type) {
113
+ sp = (0, utils_1.getEncodedTenantProduct)(access_type);
114
+ }
115
+ if (!sp && resource) {
116
+ sp = (0, utils_1.getEncodedTenantProduct)(resource);
117
+ }
118
+ if (!sp && requestedScopes) {
119
+ const encodedParams = requestedScopes.find((scope) => scope.includes('=') && scope.includes('&')); // for now assume only one encoded param i.e. for tenant/product
120
+ if (encodedParams) {
121
+ sp = (0, utils_1.getEncodedTenantProduct)(encodedParams);
122
+ }
123
+ }
124
+ if (sp && sp.tenant && sp.product) {
125
+ const { tenant, product } = sp;
126
+ requestedTenant = tenant;
127
+ requestedProduct = product;
128
+ const response = yield this.samlHandler.resolveConnection({
129
+ tenant,
130
+ product,
131
+ idp_hint,
132
+ authFlow: 'oauth',
133
+ originalParams: Object.assign({}, body),
134
+ });
135
+ if ('redirectUrl' in response) {
136
+ return {
137
+ redirect_url: response.redirectUrl,
138
+ };
139
+ }
140
+ if ('connection' in response) {
141
+ connection = response.connection;
142
+ }
143
+ }
144
+ else {
145
+ connection = yield this.connectionStore.get(client_id);
146
+ if (connection) {
147
+ requestedTenant = connection.tenant;
148
+ requestedProduct = connection.product;
149
+ }
143
150
  }
144
151
  }
152
+ else {
153
+ throw new error_1.JacksonError('You need to specify client_id or tenant & product', 403);
154
+ }
155
+ if (!connection) {
156
+ throw new error_1.JacksonError('IdP connection not found.', 403);
157
+ }
158
+ if (!allowed.redirect(redirect_uri, connection.redirectUrl)) {
159
+ throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
160
+ }
145
161
  }
146
- else {
147
- throw new error_1.JacksonError('You need to specify client_id or tenant & product', 403);
148
- }
149
- if (!connection) {
150
- throw new error_1.JacksonError('IdP connection not found.', 403);
151
- }
152
- if (!allowed.redirect(redirect_uri, connection.redirectUrl)) {
153
- throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
154
- }
155
- if (requestedOIDCFlow &&
156
- (!((_a = this.opts.openid) === null || _a === void 0 ? void 0 : _a.jwtSigningKeys) || !(0, utils_1.isJWSKeyPairLoaded)(this.opts.openid.jwtSigningKeys))) {
157
- return {
158
- redirect_url: (0, utils_1.OAuthErrorResponse)({
159
- error: 'server_error',
160
- error_description: 'OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded',
161
- redirect_uri,
162
- }),
163
- };
164
- }
165
- if (!state) {
166
- return {
167
- redirect_url: (0, utils_1.OAuthErrorResponse)({
168
- error: 'invalid_request',
169
- error_description: 'Please specify a state to safeguard against XSRF attacks',
170
- redirect_uri,
171
- }),
172
- };
162
+ catch (err) {
163
+ const error_description = (0, utils_1.getErrorMessage)(err);
164
+ // Save the error trace
165
+ yield this.samlTracer.saveTrace({
166
+ error: error_description,
167
+ context: {
168
+ tenant: requestedTenant || '',
169
+ product: requestedProduct || '',
170
+ clientID: (connection === null || connection === void 0 ? void 0 : connection.clientID) || '',
171
+ requestedOIDCFlow,
172
+ redirectUri: redirect_uri,
173
+ },
174
+ });
175
+ throw err;
173
176
  }
174
- if (response_type !== 'code') {
177
+ const isMissingJWTKeysForOIDCFlow = requestedOIDCFlow &&
178
+ (!((_a = this.opts.openid) === null || _a === void 0 ? void 0 : _a.jwtSigningKeys) || !(0, utils_1.isJWSKeyPairLoaded)(this.opts.openid.jwtSigningKeys));
179
+ const oAuthClientReqError = !state || response_type !== 'code';
180
+ const connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined;
181
+ const connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined;
182
+ if (isMissingJWTKeysForOIDCFlow || oAuthClientReqError || (!connectionIsSAML && !connectionIsOIDC)) {
183
+ let error, error_description;
184
+ if (isMissingJWTKeysForOIDCFlow) {
185
+ error = 'server_error';
186
+ error_description =
187
+ 'OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded';
188
+ }
189
+ if (!state) {
190
+ error = 'invalid_request';
191
+ error_description = 'Please specify a state to safeguard against XSRF attacks';
192
+ }
193
+ if (response_type !== 'code') {
194
+ error = 'unsupported_response_type';
195
+ error_description = 'Only Authorization Code grant is supported';
196
+ }
197
+ if (!connectionIsSAML && !connectionIsOIDC) {
198
+ error = 'server_error';
199
+ error_description = 'Connection appears to be misconfigured';
200
+ }
201
+ // Save the error trace
202
+ const traceId = yield this.samlTracer.saveTrace({
203
+ error: error_description,
204
+ context: {
205
+ tenant: requestedTenant,
206
+ product: requestedProduct,
207
+ clientID: connection.clientID,
208
+ requestedOIDCFlow,
209
+ redirectUri: redirect_uri,
210
+ },
211
+ });
175
212
  return {
176
213
  redirect_url: (0, utils_1.OAuthErrorResponse)({
177
- error: 'unsupported_response_type',
178
- error_description: 'Only Authorization Code grant is supported',
214
+ error,
215
+ error_description: traceId ? `${traceId}: ${error_description}` : error_description,
179
216
  redirect_uri,
180
217
  state,
181
218
  }),
@@ -184,37 +221,47 @@ class OAuthController {
184
221
  // Connection retrieved: Handover to IdP starts here
185
222
  let ssoUrl;
186
223
  let post = false;
187
- const connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined;
188
- const connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined;
189
224
  // Init sessionId
190
225
  const sessionId = crypto_1.default.randomBytes(16).toString('hex');
191
226
  const relayState = utils_1.relayStatePrefix + sessionId;
192
227
  // SAML connection: SAML request will be constructed here
193
228
  let samlReq;
194
- if ('idpMetadata' in connection) {
195
- const { sso } = connection.idpMetadata;
196
- if ('redirectUrl' in sso) {
197
- // HTTP Redirect binding
198
- ssoUrl = sso.redirectUrl;
199
- }
200
- else if ('postUrl' in sso) {
201
- // HTTP-POST binding
202
- ssoUrl = sso.postUrl;
203
- post = true;
204
- }
205
- else {
206
- // This code here is kept for backward compatibility. We now have validation while adding the SSO connection to ensure binding is present.
207
- return {
208
- redirect_url: (0, utils_1.OAuthErrorResponse)({
209
- error: 'invalid_request',
210
- error_description: 'SAML binding could not be retrieved',
211
- redirect_uri,
212
- state,
213
- }),
214
- };
215
- }
216
- const cert = yield (0, x509_1.getDefaultCertificate)();
229
+ if (connectionIsSAML) {
217
230
  try {
231
+ const { sso } = connection.idpMetadata;
232
+ if ('redirectUrl' in sso) {
233
+ // HTTP Redirect binding
234
+ ssoUrl = sso.redirectUrl;
235
+ }
236
+ else if ('postUrl' in sso) {
237
+ // HTTP-POST binding
238
+ ssoUrl = sso.postUrl;
239
+ post = true;
240
+ }
241
+ else {
242
+ // This code here is kept for backward compatibility. We now have validation while adding the SSO connection to ensure binding is present.
243
+ const error_description = 'SAML binding could not be retrieved';
244
+ // Save the error trace
245
+ const traceId = yield this.samlTracer.saveTrace({
246
+ error: error_description,
247
+ context: {
248
+ tenant: requestedTenant,
249
+ product: requestedProduct,
250
+ clientID: connection.clientID,
251
+ requestedOIDCFlow,
252
+ redirectUri: redirect_uri,
253
+ },
254
+ });
255
+ return {
256
+ redirect_url: (0, utils_1.OAuthErrorResponse)({
257
+ error: 'invalid_request',
258
+ error_description: traceId ? `${traceId}: ${error_description}` : error_description,
259
+ redirect_uri,
260
+ state,
261
+ }),
262
+ };
263
+ }
264
+ const cert = yield (0, x509_1.getDefaultCertificate)();
218
265
  samlReq = saml20_1.default.request({
219
266
  ssoUrl,
220
267
  entityID: this.opts.samlAudience,
@@ -228,10 +275,22 @@ class OAuthController {
228
275
  });
229
276
  }
230
277
  catch (err) {
278
+ const error_description = (0, utils_1.getErrorMessage)(err);
279
+ // Save the error trace
280
+ const traceId = yield this.samlTracer.saveTrace({
281
+ error: error_description,
282
+ context: {
283
+ tenant: requestedTenant,
284
+ product: requestedProduct,
285
+ clientID: connection.clientID,
286
+ requestedOIDCFlow,
287
+ redirectUri: redirect_uri,
288
+ },
289
+ });
231
290
  return {
232
291
  redirect_url: (0, utils_1.OAuthErrorResponse)({
233
292
  error: 'server_error',
234
- error_description: (0, utils_1.getErrorMessage)(err),
293
+ error_description: traceId ? `${traceId}: ${error_description}` : error_description,
235
294
  redirect_uri,
236
295
  state,
237
296
  }),
@@ -241,7 +300,7 @@ class OAuthController {
241
300
  // OIDC Connection: Issuer discovery, openid-client init and extraction of authorization endpoint happens here
242
301
  let oidcCodeVerifier;
243
302
  let oidcNonce;
244
- if (connectionIsOIDC && 'oidcProvider' in connection) {
303
+ if (connectionIsOIDC) {
245
304
  if (!this.opts.oidcPath) {
246
305
  return {
247
306
  redirect_url: (0, utils_1.OAuthErrorResponse)({
@@ -347,25 +406,29 @@ class OAuthController {
347
406
  authorize_form: authorizeForm,
348
407
  };
349
408
  }
350
- else if (connectionIsOIDC) {
409
+ if (connectionIsOIDC) {
351
410
  return { redirect_url: ssoUrl };
352
411
  }
353
- else {
354
- return {
355
- redirect_url: (0, utils_1.OAuthErrorResponse)({
356
- error: 'invalid_request',
357
- error_description: 'Connection appears to be misconfigured',
358
- redirect_uri,
359
- state,
360
- }),
361
- };
362
- }
412
+ throw 'Connection appears to be misconfigured';
363
413
  }
364
414
  catch (err) {
415
+ const error_description = (0, utils_1.getErrorMessage)(err);
416
+ // Save the error trace
417
+ const traceId = yield this.samlTracer.saveTrace({
418
+ error: error_description,
419
+ context: {
420
+ tenant: requestedTenant,
421
+ product: requestedProduct,
422
+ clientID: connection.clientID,
423
+ requestedOIDCFlow,
424
+ redirectUri: redirect_uri,
425
+ samlRequest: (samlReq === null || samlReq === void 0 ? void 0 : samlReq.request) || '',
426
+ },
427
+ });
365
428
  return {
366
429
  redirect_url: (0, utils_1.OAuthErrorResponse)({
367
430
  error: 'server_error',
368
- error_description: (0, utils_1.getErrorMessage)(err),
431
+ error_description: traceId ? `${traceId}: ${error_description}` : error_description,
369
432
  redirect_uri,
370
433
  state,
371
434
  }),
@@ -376,112 +439,159 @@ class OAuthController {
376
439
  samlResponse(body) {
377
440
  var _a;
378
441
  return __awaiter(this, void 0, void 0, function* () {
379
- const { SAMLResponse, idp_hint, RelayState = '' } = body;
380
- const isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
381
- // IdP is disabled so block the request
382
- if (!this.opts.idpEnabled && isIdPFlow) {
383
- // IdP login is disabled so block the request
384
- throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
385
- }
386
- const sessionId = RelayState.replace(utils_1.relayStatePrefix, '');
387
- const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
388
- const issuer = saml20_1.default.parseIssuer(rawResponse);
389
- if (!issuer) {
390
- throw new error_1.JacksonError('Issuer not found.', 403);
391
- }
392
- const connections = yield this.connectionStore.getByIndex({
393
- name: utils_1.IndexNames.EntityID,
394
- value: issuer,
395
- });
396
- if (!connections || connections.length === 0) {
397
- throw new error_1.JacksonError('SAML connection not found.', 403);
398
- }
399
- const session = sessionId ? yield this.sessionStore.get(sessionId) : null;
400
- if (!isIdPFlow && !session) {
401
- throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
402
- }
403
- const isSAMLFederated = session && 'samlFederated' in session;
404
- const isSPFflow = !isIdPFlow && !isSAMLFederated;
405
442
  let connection;
406
- // IdP initiated SSO flow
407
- if (isIdPFlow) {
408
- const response = yield this.samlHandler.resolveConnection({
409
- idp_hint,
410
- authFlow: 'idp-initiated',
411
- entityId: issuer,
412
- originalParams: {
413
- SAMLResponse,
414
- },
443
+ let rawResponse;
444
+ let sessionId;
445
+ let session;
446
+ let issuer;
447
+ let isIdPFlow;
448
+ let isSAMLFederated;
449
+ let validateOpts;
450
+ let redirect_uri;
451
+ const { SAMLResponse, idp_hint, RelayState = '' } = body;
452
+ try {
453
+ isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
454
+ rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
455
+ issuer = saml20_1.default.parseIssuer(rawResponse);
456
+ if (!this.opts.idpEnabled && isIdPFlow) {
457
+ // IdP login is disabled so block the request
458
+ throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
459
+ }
460
+ sessionId = RelayState.replace(utils_1.relayStatePrefix, '');
461
+ if (!issuer) {
462
+ throw new error_1.JacksonError('Issuer not found.', 403);
463
+ }
464
+ const connections = yield this.connectionStore.getByIndex({
465
+ name: utils_1.IndexNames.EntityID,
466
+ value: issuer,
415
467
  });
416
- // Redirect to the product selection page
417
- if ('postForm' in response) {
418
- return {
419
- app_select_form: response.postForm,
420
- };
468
+ if (!connections || connections.length === 0) {
469
+ throw new error_1.JacksonError('SAML connection not found.', 403);
421
470
  }
422
- // Found a connection
423
- if ('connection' in response) {
424
- connection = response.connection;
471
+ session = sessionId ? yield this.sessionStore.get(sessionId) : null;
472
+ if (!isIdPFlow && !session) {
473
+ throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
425
474
  }
475
+ isSAMLFederated = session && 'samlFederated' in session;
476
+ const isSPFflow = !isIdPFlow && !isSAMLFederated;
477
+ // IdP initiated SSO flow
478
+ if (isIdPFlow) {
479
+ const response = yield this.samlHandler.resolveConnection({
480
+ idp_hint,
481
+ authFlow: 'idp-initiated',
482
+ entityId: issuer,
483
+ originalParams: {
484
+ SAMLResponse,
485
+ },
486
+ });
487
+ // Redirect to the product selection page
488
+ if ('postForm' in response) {
489
+ return {
490
+ app_select_form: response.postForm,
491
+ };
492
+ }
493
+ // Found a connection
494
+ if ('connection' in response) {
495
+ connection = response.connection;
496
+ }
497
+ }
498
+ // SP initiated SSO flow
499
+ // Resolve if there are multiple matches for SP login
500
+ if (isSPFflow) {
501
+ connection = connections.filter((c) => {
502
+ return (c.clientID === session.requested.client_id ||
503
+ (c.tenant === session.requested.tenant && c.product === session.requested.product));
504
+ })[0];
505
+ }
506
+ if (!connection) {
507
+ connection = connections[0];
508
+ }
509
+ if (!connection) {
510
+ throw new error_1.JacksonError('SAML connection not found.', 403);
511
+ }
512
+ if (session &&
513
+ session.redirect_uri &&
514
+ !allowed.redirect(session.redirect_uri, connection.redirectUrl)) {
515
+ throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
516
+ }
517
+ const { privateKey } = yield (0, x509_1.getDefaultCertificate)();
518
+ validateOpts = {
519
+ thumbprint: `${connection === null || connection === void 0 ? void 0 : connection.idpMetadata.thumbprint}`,
520
+ audience: `${this.opts.samlAudience}`,
521
+ privateKey,
522
+ };
523
+ if (session && session.id) {
524
+ validateOpts['inResponseTo'] = session.id;
525
+ }
526
+ redirect_uri = (session && session.redirect_uri) || connection.defaultRedirectUrl;
426
527
  }
427
- // SP initiated SSO flow
428
- // Resolve if there are multiple matches for SP login
429
- if (isSPFflow) {
430
- connection = connections.filter((c) => {
431
- return (c.clientID === session.requested.client_id ||
432
- (c.tenant === session.requested.tenant && c.product === session.requested.product));
433
- })[0];
434
- }
435
- if (!connection) {
436
- connection = connections[0];
437
- }
438
- if (!connection) {
439
- throw new error_1.JacksonError('SAML connection not found.', 403);
440
- }
441
- if (session &&
442
- session.redirect_uri &&
443
- !allowed.redirect(session.redirect_uri, connection.redirectUrl)) {
444
- throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
445
- }
446
- const { privateKey } = yield (0, x509_1.getDefaultCertificate)();
447
- const validateOpts = {
448
- thumbprint: `${connection.idpMetadata.thumbprint}`,
449
- audience: `${this.opts.samlAudience}`,
450
- privateKey,
451
- };
452
- if (session && session.id) {
453
- validateOpts['inResponseTo'] = session.id;
528
+ catch (err) {
529
+ // Save the error trace
530
+ yield this.samlTracer.saveTrace({
531
+ error: (0, utils_1.getErrorMessage)(err),
532
+ context: {
533
+ samlResponse: rawResponse,
534
+ tenant: (connection === null || connection === void 0 ? void 0 : connection.tenant) || '',
535
+ product: (connection === null || connection === void 0 ? void 0 : connection.product) || '',
536
+ clientID: (connection === null || connection === void 0 ? void 0 : connection.clientID) || '',
537
+ redirectUri: isIdPFlow ? connection === null || connection === void 0 ? void 0 : connection.defaultRedirectUrl : session === null || session === void 0 ? void 0 : session.redirect_uri,
538
+ issuer: issuer || '',
539
+ isSAMLFederated: !!isSAMLFederated,
540
+ isIdPFlow: !!isIdPFlow,
541
+ relayState: RelayState,
542
+ },
543
+ });
544
+ throw err; // Rethrow the error
454
545
  }
455
- const redirect_uri = (session && session.redirect_uri) || connection.defaultRedirectUrl;
456
- let profile = null;
546
+ let profile;
457
547
  try {
458
548
  profile = yield (0, lib_1.extractSAMLResponseAttributes)(rawResponse, validateOpts);
549
+ // This is a federated SAML flow, let's create a new SAMLResponse and POST it to the SP
550
+ if (isSAMLFederated) {
551
+ const { responseForm } = yield this.samlHandler.createSAMLResponse({ profile, session });
552
+ yield this.sessionStore.delete(sessionId);
553
+ return { responseForm };
554
+ }
555
+ const code = yield this._buildAuthorizationCode(connection, profile, session, isIdPFlow);
556
+ const params = {
557
+ code,
558
+ };
559
+ if (session && session.state) {
560
+ params['state'] = session.state;
561
+ }
562
+ yield this.sessionStore.delete(sessionId);
563
+ return { redirect_url: redirect.success(redirect_uri, params) };
459
564
  }
460
565
  catch (err) {
566
+ const error_description = (0, utils_1.getErrorMessage)(err);
567
+ // Trace the error
568
+ const traceId = yield this.samlTracer.saveTrace({
569
+ error: error_description,
570
+ context: {
571
+ samlResponse: rawResponse,
572
+ tenant: connection.tenant,
573
+ product: connection.product,
574
+ clientID: connection.clientID,
575
+ redirectUri: isIdPFlow ? connection === null || connection === void 0 ? void 0 : connection.defaultRedirectUrl : session === null || session === void 0 ? void 0 : session.redirect_uri,
576
+ isSAMLFederated,
577
+ isIdPFlow,
578
+ relayState: RelayState,
579
+ issuer,
580
+ profile,
581
+ },
582
+ });
583
+ if (isSAMLFederated) {
584
+ throw err;
585
+ }
461
586
  return {
462
587
  redirect_url: (0, utils_1.OAuthErrorResponse)({
463
588
  error: 'access_denied',
464
- error_description: (0, utils_1.getErrorMessage)(err),
589
+ error_description: traceId ? `${traceId}: ${error_description}` : error_description,
465
590
  redirect_uri,
466
591
  state: (_a = session.requested) === null || _a === void 0 ? void 0 : _a.state,
467
592
  }),
468
593
  };
469
594
  }
470
- // This is a federated SAML flow, let's create a new SAMLResponse and POST it to the SP
471
- if (isSAMLFederated) {
472
- const { responseForm } = yield this.samlHandler.createSAMLResponse({ profile, session });
473
- yield this.sessionStore.delete(sessionId);
474
- return { responseForm };
475
- }
476
- const code = yield this._buildAuthorizationCode(connection, profile, session, isIdPFlow);
477
- const params = {
478
- code,
479
- };
480
- if (session && session.state) {
481
- params['state'] = session.state;
482
- }
483
- yield this.sessionStore.delete(sessionId);
484
- return { redirect_url: redirect.success(redirect_uri, params) };
485
595
  });
486
596
  }
487
597
  oidcAuthzResponse(body) {