@boxyhq/saml-jackson 1.35.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/controller/analytics.d.ts +3 -1
  2. package/dist/controller/analytics.js +3 -2
  3. package/dist/controller/analytics.js.map +1 -1
  4. package/dist/controller/api.d.ts +1 -3
  5. package/dist/controller/api.js +5 -6
  6. package/dist/controller/api.js.map +1 -1
  7. package/dist/controller/connection/oidc.d.ts +2 -3
  8. package/dist/controller/connection/oidc.js +2 -26
  9. package/dist/controller/connection/oidc.js.map +1 -1
  10. package/dist/controller/connection/saml.d.ts +2 -3
  11. package/dist/controller/connection/saml.js +4 -28
  12. package/dist/controller/connection/saml.js.map +1 -1
  13. package/dist/controller/error.d.ts +2 -1
  14. package/dist/controller/error.js +4 -1
  15. package/dist/controller/error.js.map +1 -1
  16. package/dist/controller/oauth/oidc-client.js +1 -1
  17. package/dist/controller/oauth/oidc-client.js.map +1 -1
  18. package/dist/controller/oauth.d.ts +3 -0
  19. package/dist/controller/oauth.js +212 -110
  20. package/dist/controller/oauth.js.map +1 -1
  21. package/dist/controller/sso-handler.d.ts +2 -2
  22. package/dist/controller/sso-handler.js +73 -65
  23. package/dist/controller/sso-handler.js.map +1 -1
  24. package/dist/controller/utils.d.ts +1 -0
  25. package/dist/controller/utils.js +6 -1
  26. package/dist/controller/utils.js.map +1 -1
  27. package/dist/cron/lock.d.ts +4 -2
  28. package/dist/cron/lock.js +4 -3
  29. package/dist/cron/lock.js.map +1 -1
  30. package/dist/db/db.d.ts +5 -2
  31. package/dist/db/db.js +10 -9
  32. package/dist/db/db.js.map +1 -1
  33. package/dist/db/dynamoDb.d.ts +3 -1
  34. package/dist/db/dynamoDb.js +9 -9
  35. package/dist/db/dynamoDb.js.map +1 -1
  36. package/dist/db/mem.d.ts +3 -1
  37. package/dist/db/mem.js +1 -1
  38. package/dist/db/mem.js.map +1 -1
  39. package/dist/db/mongo.d.ts +7 -3
  40. package/dist/db/mongo.js +5 -4
  41. package/dist/db/mongo.js.map +1 -1
  42. package/dist/db/redis.d.ts +7 -3
  43. package/dist/db/redis.js +4 -3
  44. package/dist/db/redis.js.map +1 -1
  45. package/dist/db/sql/sql.d.ts +7 -3
  46. package/dist/db/sql/sql.js +4 -3
  47. package/dist/db/sql/sql.js.map +1 -1
  48. package/dist/directory-sync/batch-events/queue.d.ts +2 -2
  49. package/dist/directory-sync/batch-events/queue.js +13 -13
  50. package/dist/directory-sync/batch-events/queue.js.map +1 -1
  51. package/dist/directory-sync/index.d.ts +2 -2
  52. package/dist/directory-sync/index.js +2 -2
  53. package/dist/directory-sync/index.js.map +1 -1
  54. package/dist/directory-sync/non-scim/google/oauth.js +6 -6
  55. package/dist/directory-sync/non-scim/google/oauth.js.map +1 -1
  56. package/dist/directory-sync/non-scim/index.d.ts +2 -2
  57. package/dist/directory-sync/non-scim/index.js +4 -4
  58. package/dist/directory-sync/non-scim/index.js.map +1 -1
  59. package/dist/directory-sync/utils.d.ts +2 -2
  60. package/dist/directory-sync/utils.js +3 -3
  61. package/dist/directory-sync/utils.js.map +1 -1
  62. package/dist/ee/identity-federation/idp-login.js +3 -3
  63. package/dist/ee/identity-federation/idp-login.js.map +1 -1
  64. package/dist/ee/identity-federation/index.d.ts +2 -2
  65. package/dist/ee/identity-federation/sso.d.ts +2 -2
  66. package/dist/ee/identity-federation/sso.js +4 -4
  67. package/dist/ee/identity-federation/sso.js.map +1 -1
  68. package/dist/event/axios.d.ts +2 -2
  69. package/dist/event/axios.js +26 -22
  70. package/dist/event/axios.js.map +1 -1
  71. package/dist/event/index.d.ts +3 -2
  72. package/dist/event/index.js +2 -1
  73. package/dist/event/index.js.map +1 -1
  74. package/dist/event/webhook.d.ts +2 -2
  75. package/dist/event/webhook.js +5 -4
  76. package/dist/event/webhook.js.map +1 -1
  77. package/dist/index.js +24 -11
  78. package/dist/index.js.map +1 -1
  79. package/dist/opentelemetry/metrics.d.ts +6 -1
  80. package/dist/opentelemetry/metrics.js +26 -2
  81. package/dist/opentelemetry/metrics.js.map +1 -1
  82. package/dist/sso-traces/index.d.ts +2 -2
  83. package/dist/sso-traces/index.js +1 -1
  84. package/dist/sso-traces/index.js.map +1 -1
  85. package/dist/typings.d.ts +16 -7
  86. package/package.json +8 -8
  87. package/dist/ee/ory/ory.d.ts +0 -18
  88. package/dist/ee/ory/ory.js +0 -202
  89. package/dist/ee/ory/ory.js.map +0 -1
@@ -72,7 +72,24 @@ const x509_1 = require("../saml/x509");
72
72
  const sso_handler_1 = require("./sso-handler");
73
73
  const lib_1 = require("../saml/lib");
74
74
  const oidc_client_1 = require("./oauth/oidc-client");
75
+ const encrypter = __importStar(require("../db/encrypter"));
75
76
  const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
77
+ function encrypt(val) {
78
+ const genKey = crypto_1.default.randomBytes(32);
79
+ const hexKey = genKey.toString('hex');
80
+ const encVal = encrypter.encrypt(JSON.stringify(val), genKey);
81
+ return {
82
+ hexKey,
83
+ encVal,
84
+ };
85
+ }
86
+ function decrypt(res, encryptionKey) {
87
+ const encKey = Buffer.from(encryptionKey, 'hex');
88
+ if (res.iv && res.tag) {
89
+ return JSON.parse(encrypter.decrypt(res.value, res.iv, res.tag, encKey));
90
+ }
91
+ return JSON.parse(res.value);
92
+ }
76
93
  class OAuthController {
77
94
  constructor({ connectionStore, sessionStore, codeStore, tokenStore, ssoTraces, opts, idFedApp }) {
78
95
  this.connectionStore = connectionStore;
@@ -100,6 +117,10 @@ class OAuthController {
100
117
  let isOIDCFederated;
101
118
  let connection;
102
119
  let fedApp;
120
+ let connectionIsSAML;
121
+ let connectionIsOIDC;
122
+ let protocol;
123
+ const login_type = 'sp-initiated';
103
124
  try {
104
125
  requestedTenant = tenant;
105
126
  requestedProduct = product;
@@ -169,6 +190,7 @@ class OAuthController {
169
190
  // First we check if it's a federated connection
170
191
  if (client_id.startsWith(`${utils_1.clientIDFederatedPrefix}${utils_1.clientIDOIDCPrefix}`)) {
171
192
  isOIDCFederated = true;
193
+ protocol = 'oidc-federation';
172
194
  fedApp = yield this.idFedApp.get({
173
195
  id: client_id.replace(utils_1.clientIDFederatedPrefix, ''),
174
196
  });
@@ -207,8 +229,11 @@ class OAuthController {
207
229
  throw new error_1.JacksonError('You need to specify client_id or tenant & product', 403);
208
230
  }
209
231
  if (!connection) {
210
- throw new error_1.JacksonError('IdP connection not found.', 403);
232
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'IdP connection not found.');
211
233
  }
234
+ connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined;
235
+ connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined;
236
+ protocol = isOIDCFederated ? 'oidc-federation' : connectionIsSAML ? 'saml' : 'oidc';
212
237
  if (!allowed.redirect(redirect_uri, connection.redirectUrl)) {
213
238
  if (fedApp) {
214
239
  if (!allowed.redirect(redirect_uri, fedApp.redirectUrl)) {
@@ -220,11 +245,15 @@ class OAuthController {
220
245
  }
221
246
  }
222
247
  if (!(0, utils_1.isConnectionActive)(connection)) {
223
- throw new error_1.JacksonError('SSO connection is deactivated. Please contact your administrator.', 403);
248
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'SSO connection is deactivated.');
224
249
  }
225
250
  }
226
251
  catch (err) {
227
252
  const error_description = (0, utils_1.getErrorMessage)(err);
253
+ metrics.increment('oauthAuthorizeError', {
254
+ protocol,
255
+ login_type,
256
+ });
228
257
  // Save the error trace
229
258
  yield this.ssoTraces.saveTrace({
230
259
  error: error_description,
@@ -242,14 +271,14 @@ class OAuthController {
242
271
  const isMissingJWTKeysForOIDCFlow = requestedOIDCFlow &&
243
272
  (!((_a = this.opts.openid) === null || _a === void 0 ? void 0 : _a.jwtSigningKeys) || !(0, utils_1.isJWSKeyPairLoaded)(this.opts.openid.jwtSigningKeys));
244
273
  const oAuthClientReqError = !state || response_type !== 'code';
245
- const connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined;
246
- const connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined;
247
274
  if (isMissingJWTKeysForOIDCFlow || oAuthClientReqError || (!connectionIsSAML && !connectionIsOIDC)) {
248
- let error, error_description;
275
+ let error, error_description, internalError;
249
276
  if (isMissingJWTKeysForOIDCFlow) {
250
277
  error = 'server_error';
251
- error_description =
252
- 'OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded';
278
+ internalError =
279
+ 'Authorize error: OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded';
280
+ error_description = utils_1.GENERIC_ERR_STRING;
281
+ this.opts.logger.error(internalError);
253
282
  }
254
283
  if (!state) {
255
284
  error = 'invalid_request';
@@ -261,11 +290,17 @@ class OAuthController {
261
290
  }
262
291
  if (!connectionIsSAML && !connectionIsOIDC) {
263
292
  error = 'server_error';
264
- error_description = 'Connection appears to be misconfigured';
293
+ internalError = 'Authorize error: Connection appears to be misconfigured';
294
+ error_description = utils_1.GENERIC_ERR_STRING;
295
+ this.opts.logger.error(internalError);
265
296
  }
297
+ metrics.increment('oauthAuthorizeError', {
298
+ protocol,
299
+ login_type,
300
+ });
266
301
  // Save the error trace
267
302
  const traceId = yield this.ssoTraces.saveTrace({
268
- error: error_description,
303
+ error: internalError !== null && internalError !== void 0 ? internalError : error_description,
269
304
  context: {
270
305
  tenant: requestedTenant,
271
306
  product: requestedProduct,
@@ -282,6 +317,7 @@ class OAuthController {
282
317
  redirect_uri,
283
318
  state,
284
319
  }),
320
+ error: `${error} - ${error_description}`,
285
321
  };
286
322
  }
287
323
  // Connection retrieved: Handover to IdP starts here
@@ -291,7 +327,7 @@ class OAuthController {
291
327
  const sessionId = crypto_1.default.randomBytes(16).toString('hex');
292
328
  const relayState = utils_1.relayStatePrefix + sessionId;
293
329
  // SAML connection: SAML request will be constructed here
294
- let samlReq;
330
+ let samlReq, internalError;
295
331
  if (connectionIsSAML) {
296
332
  try {
297
333
  const { sso } = connection.idpMetadata;
@@ -306,10 +342,16 @@ class OAuthController {
306
342
  }
307
343
  else {
308
344
  // This code here is kept for backward compatibility. We now have validation while adding the SSO connection to ensure binding is present.
309
- const error_description = 'SAML binding could not be retrieved';
345
+ internalError = 'Authorize error: SAML binding could not be retrieved';
346
+ const error_description = utils_1.GENERIC_ERR_STRING;
347
+ this.opts.logger.error(internalError);
348
+ metrics.increment('oauthAuthorizeError', {
349
+ protocol,
350
+ login_type,
351
+ });
310
352
  // Save the error trace
311
353
  const traceId = yield this.ssoTraces.saveTrace({
312
- error: error_description,
354
+ error: internalError !== null && internalError !== void 0 ? internalError : error_description,
313
355
  context: {
314
356
  tenant: requestedTenant,
315
357
  product: requestedProduct,
@@ -343,6 +385,11 @@ class OAuthController {
343
385
  }
344
386
  catch (err) {
345
387
  const error_description = (0, utils_1.getErrorMessage)(err);
388
+ this.opts.logger.error(`Authorize error: ${error_description} `);
389
+ metrics.increment('oauthAuthorizeError', {
390
+ protocol,
391
+ login_type,
392
+ });
346
393
  // Save the error trace
347
394
  const traceId = yield this.ssoTraces.saveTrace({
348
395
  error: error_description,
@@ -373,7 +420,7 @@ class OAuthController {
373
420
  const { ssoTraces } = this;
374
421
  try {
375
422
  if (!this.opts.oidcPath) {
376
- throw new error_1.JacksonError('OpenID response handler path (oidcPath) is not set');
423
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 500, 'OpenID response handler path (oidcPath) is not set');
377
424
  }
378
425
  const client = (yield (0, utils_1.dynamicImport)('openid-client'));
379
426
  const oidcConfig = yield (0, oidc_client_1.oidcClientConfig)({
@@ -409,6 +456,11 @@ class OAuthController {
409
456
  }
410
457
  catch (err) {
411
458
  const error_description = (0, utils_1.getErrorMessage)(err);
459
+ this.opts.logger.error(`Authorize error: ${error_description}`);
460
+ metrics.increment('oauthAuthorizeError', {
461
+ protocol,
462
+ login_type,
463
+ });
412
464
  // Save the error trace
413
465
  const traceId = yield this.ssoTraces.saveTrace({
414
466
  error: error_description,
@@ -435,7 +487,7 @@ class OAuthController {
435
487
  }
436
488
  // Session persistence happens here
437
489
  try {
438
- const requested = { client_id, state, redirect_uri };
490
+ const requested = { client_id, state, redirect_uri, protocol, login_type };
439
491
  if (requestedTenant) {
440
492
  requested.tenant = requestedTenant;
441
493
  }
@@ -513,6 +565,10 @@ class OAuthController {
513
565
  }
514
566
  catch (err) {
515
567
  const error_description = (0, utils_1.getErrorMessage)(err);
568
+ metrics.increment('oauthAuthorizeError', {
569
+ protocol,
570
+ login_type,
571
+ });
516
572
  // Save the error trace
517
573
  const traceId = yield this.ssoTraces.saveTrace({
518
574
  error: error_description,
@@ -551,24 +607,29 @@ class OAuthController {
551
607
  let validateOpts;
552
608
  let redirect_uri;
553
609
  const { SAMLResponse, idp_hint, RelayState = '' } = body;
610
+ let protocol, login_type;
554
611
  try {
555
612
  isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
556
613
  rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
557
614
  issuer = saml20_1.default.parseIssuer(rawResponse);
558
615
  if (!this.opts.idpEnabled && isIdPFlow) {
559
616
  // IdP login is disabled so block the request
560
- throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
617
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.');
618
+ }
619
+ login_type = isIdPFlow ? 'idp-initiated' : 'sp-initiated';
620
+ if (isIdPFlow) {
621
+ protocol = 'saml';
561
622
  }
562
623
  sessionId = RelayState.replace(utils_1.relayStatePrefix, '');
563
624
  if (!issuer) {
564
- throw new error_1.JacksonError('Issuer not found.', 403);
625
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'Issuer not found.');
565
626
  }
566
627
  const connections = (yield this.connectionStore.getByIndex({
567
628
  name: utils_1.IndexNames.EntityID,
568
629
  value: issuer,
569
630
  })).data;
570
631
  if (!connections || connections.length === 0) {
571
- throw new error_1.JacksonError('SAML connection not found.', 403);
632
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'SAML connection not found.');
572
633
  }
573
634
  session = sessionId ? yield this.sessionStore.get(sessionId) : null;
574
635
  if (!isIdPFlow && !session) {
@@ -577,6 +638,7 @@ class OAuthController {
577
638
  isSAMLFederated = session && 'samlFederated' in session;
578
639
  isOIDCFederated = session && 'oidcFederated' in session;
579
640
  const isSPFlow = !isIdPFlow && !isSAMLFederated;
641
+ protocol = isOIDCFederated ? 'oidc-federation' : isSAMLFederated ? 'saml-federation' : 'saml';
580
642
  // IdP initiated SSO flow
581
643
  if (isIdPFlow) {
582
644
  const response = yield this.ssoHandler.resolveConnection({
@@ -597,7 +659,7 @@ class OAuthController {
597
659
  if ('connection' in response) {
598
660
  connection = response.connection;
599
661
  if (!(0, utils_1.isConnectionActive)(connection)) {
600
- throw new error_1.JacksonError('SSO connection is deactivated. Please contact your administrator.', 403);
662
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'SSO connection is deactivated. Please contact your administrator.');
601
663
  }
602
664
  }
603
665
  }
@@ -611,7 +673,7 @@ class OAuthController {
611
673
  })[0];
612
674
  }
613
675
  if (!connection) {
614
- throw new error_1.JacksonError('SAML connection not found.', 403);
676
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'SAML connection not found.');
615
677
  }
616
678
  if (session &&
617
679
  session.redirect_uri &&
@@ -642,6 +704,7 @@ class OAuthController {
642
704
  redirect_uri = (session && session.redirect_uri) || connection.defaultRedirectUrl;
643
705
  }
644
706
  catch (err) {
707
+ metrics.increment('oAuthResponseError', { protocol, login_type });
645
708
  // Save the error trace
646
709
  yield this.ssoTraces.saveTrace({
647
710
  error: (0, utils_1.getErrorMessage)(err),
@@ -684,7 +747,9 @@ class OAuthController {
684
747
  return { redirect_url: redirect.success(redirect_uri, params) };
685
748
  }
686
749
  catch (err) {
750
+ metrics.increment('oAuthResponseError', { protocol, login_type });
687
751
  const error_description = (0, utils_1.getErrorMessage)(err);
752
+ this.opts.logger.error(`SAMLResponse error: ${error_description}`);
688
753
  // Trace the error
689
754
  const traceId = yield this.ssoTraces.saveTrace({
690
755
  error: error_description,
@@ -716,6 +781,7 @@ class OAuthController {
716
781
  redirect_uri,
717
782
  state: (_o = session === null || session === void 0 ? void 0 : session.requested) === null || _o === void 0 ? void 0 : _o.state,
718
783
  }),
784
+ error: `access_denied - ${error_description}`,
719
785
  };
720
786
  }
721
787
  });
@@ -729,6 +795,8 @@ class OAuthController {
729
795
  let isOIDCFederated;
730
796
  let redirect_uri;
731
797
  let profile;
798
+ let protocol;
799
+ const login_type = 'sp-initiated';
732
800
  const callbackParams = body;
733
801
  let RelayState = callbackParams.state || '';
734
802
  try {
@@ -742,9 +810,10 @@ class OAuthController {
742
810
  }
743
811
  isSAMLFederated = session && 'samlFederated' in session;
744
812
  isOIDCFederated = session && 'oidcFederated' in session;
813
+ protocol = isOIDCFederated ? 'oidc-federation' : isSAMLFederated ? 'saml-federation' : 'oidc';
745
814
  oidcConnection = yield this.connectionStore.get(session.id);
746
815
  if (!oidcConnection) {
747
- throw new error_1.JacksonError('OIDC connection not found.', 403);
816
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 403, 'OIDC connection not found.');
748
817
  }
749
818
  if (!isSAMLFederated) {
750
819
  redirect_uri = session && session.redirect_uri;
@@ -764,6 +833,7 @@ class OAuthController {
764
833
  }
765
834
  }
766
835
  catch (err) {
836
+ metrics.increment('oAuthResponseError', { protocol, login_type });
767
837
  yield this.ssoTraces.saveTrace({
768
838
  error: (0, utils_1.getErrorMessage)(err),
769
839
  context: {
@@ -839,6 +909,8 @@ class OAuthController {
839
909
  catch (err) {
840
910
  const { error, error_description, error_uri, session_state, scope, stack } = err;
841
911
  const error_message = error_description || (0, utils_1.getErrorMessage)(err);
912
+ this.opts.logger.error(`OIDCResponse error: ${error_message}`);
913
+ metrics.increment('oAuthResponseError', { protocol, login_type });
842
914
  const traceId = yield this.ssoTraces.saveTrace({
843
915
  error: error_message,
844
916
  context: {
@@ -874,6 +946,7 @@ class OAuthController {
874
946
  redirect_uri: redirect_uri,
875
947
  state: session.state,
876
948
  }),
949
+ error: `${error} - ${error_message}`,
877
950
  };
878
951
  }
879
952
  });
@@ -898,8 +971,9 @@ class OAuthController {
898
971
  if (session) {
899
972
  codeVal['session'] = session;
900
973
  }
901
- yield this.codeStore.put(code, codeVal);
902
- return code;
974
+ const { hexKey, encVal } = encrypt(codeVal);
975
+ yield this.codeStore.put(code, encVal);
976
+ return hexKey + '.' + code;
903
977
  });
904
978
  }
905
979
  /**
@@ -966,6 +1040,7 @@ class OAuthController {
966
1040
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
967
1041
  let basic_client_id;
968
1042
  let basic_client_secret;
1043
+ let protocol, login_type;
969
1044
  try {
970
1045
  if (authHeader) {
971
1046
  // Authorization: Basic {Base64(<client_id>:<client_secret>)}
@@ -983,111 +1058,129 @@ class OAuthController {
983
1058
  const client_secret = 'client_secret' in body ? body.client_secret : basic_client_secret;
984
1059
  const code_verifier = 'code_verifier' in body ? body.code_verifier : undefined;
985
1060
  metrics.increment('oauthToken');
986
- if (grant_type !== 'authorization_code') {
987
- throw new error_1.JacksonError('Unsupported grant_type', 400);
988
- }
989
- if (!code) {
990
- throw new error_1.JacksonError('Please specify code', 400);
991
- }
992
- const codeVal = yield this.codeStore.get(code);
993
- if (!codeVal || !codeVal.profile) {
994
- throw new error_1.JacksonError('Invalid code', 403);
995
- }
996
- if ((_a = codeVal.requested) === null || _a === void 0 ? void 0 : _a.redirect_uri) {
997
- if (redirect_uri !== codeVal.requested.redirect_uri) {
998
- throw new error_1.JacksonError(`Invalid request: ${!redirect_uri ? 'redirect_uri missing' : 'redirect_uri mismatch'}`, 400);
1061
+ try {
1062
+ if (grant_type !== 'authorization_code') {
1063
+ throw new error_1.JacksonError('Unsupported grant_type', 400);
999
1064
  }
1000
- }
1001
- if (code_verifier) {
1002
- // PKCE flow
1003
- let cv = code_verifier;
1004
- if (((_b = codeVal.session.code_challenge_method) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 's256') {
1005
- cv = codeVerifier.encode(code_verifier);
1065
+ if (!code) {
1066
+ throw new error_1.JacksonError('Please specify code', 400);
1006
1067
  }
1007
- if (codeVal.session.code_challenge !== cv) {
1008
- throw new error_1.JacksonError('Invalid code_verifier', 401);
1068
+ const codes = code.split('.');
1069
+ if (codes.length !== 2) {
1070
+ throw new error_1.JacksonError('Invalid code', 403);
1009
1071
  }
1010
- // For Federation flow, we need to verify the client_secret
1011
- if (client_id === null || client_id === void 0 ? void 0 : client_id.startsWith(`${utils_1.clientIDFederatedPrefix}${utils_1.clientIDOIDCPrefix}`)) {
1012
- if (client_id !== ((_d = (_c = codeVal.session) === null || _c === void 0 ? void 0 : _c.oidcFederated) === null || _d === void 0 ? void 0 : _d.clientID) ||
1013
- client_secret !== ((_f = (_e = codeVal.session) === null || _e === void 0 ? void 0 : _e.oidcFederated) === null || _f === void 0 ? void 0 : _f.clientSecret)) {
1014
- throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
1072
+ const encCodeVal = yield this.codeStore.get(codes[1]);
1073
+ if (!encCodeVal) {
1074
+ throw new error_1.JacksonError('Invalid code', 403);
1075
+ }
1076
+ const codeVal = decrypt(encCodeVal, codes[0]);
1077
+ if (!codeVal || !codeVal.profile) {
1078
+ throw new error_1.JacksonError('Invalid code', 403);
1079
+ }
1080
+ protocol = codeVal.requested.protocol || 'saml';
1081
+ login_type = codeVal.isIdPFlow ? 'idp-initiated' : 'sp-initiated';
1082
+ if ((_a = codeVal.requested) === null || _a === void 0 ? void 0 : _a.redirect_uri) {
1083
+ if (redirect_uri !== codeVal.requested.redirect_uri) {
1084
+ throw new error_1.JacksonError(`Invalid request: ${!redirect_uri ? 'redirect_uri missing' : 'redirect_uri mismatch'}`, 400);
1015
1085
  }
1016
1086
  }
1017
- }
1018
- else if (client_id && client_secret) {
1019
- // check if we have an encoded client_id
1020
- if (client_id !== 'dummy') {
1021
- const sp = (0, utils_1.getEncodedTenantProduct)(client_id);
1022
- if (!sp) {
1023
- // OAuth flow
1024
- if (client_id !== codeVal.clientID || client_secret !== codeVal.clientSecret) {
1087
+ if (code_verifier) {
1088
+ // PKCE flow
1089
+ let cv = code_verifier;
1090
+ if (((_b = codeVal.session.code_challenge_method) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 's256') {
1091
+ cv = codeVerifier.encode(code_verifier);
1092
+ }
1093
+ if (codeVal.session.code_challenge !== cv) {
1094
+ throw new error_1.JacksonError('Invalid code_verifier', 401);
1095
+ }
1096
+ // For Federation flow, we need to verify the client_secret
1097
+ if (client_id === null || client_id === void 0 ? void 0 : client_id.startsWith(`${utils_1.clientIDFederatedPrefix}${utils_1.clientIDOIDCPrefix}`)) {
1098
+ if (client_id !== ((_d = (_c = codeVal.session) === null || _c === void 0 ? void 0 : _c.oidcFederated) === null || _d === void 0 ? void 0 : _d.clientID) ||
1099
+ client_secret !== ((_f = (_e = codeVal.session) === null || _e === void 0 ? void 0 : _e.oidcFederated) === null || _f === void 0 ? void 0 : _f.clientSecret)) {
1025
1100
  throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
1026
1101
  }
1027
1102
  }
1028
- else {
1029
- if (!codeVal.isIdPFlow &&
1030
- (sp.tenant !== ((_g = codeVal.requested) === null || _g === void 0 ? void 0 : _g.tenant) || sp.product !== ((_h = codeVal.requested) === null || _h === void 0 ? void 0 : _h.product))) {
1031
- throw new error_1.JacksonError('Invalid tenant or product', 401);
1103
+ }
1104
+ else if (client_id && client_secret) {
1105
+ // check if we have an encoded client_id
1106
+ if (client_id !== 'dummy') {
1107
+ const sp = (0, utils_1.getEncodedTenantProduct)(client_id);
1108
+ if (!sp) {
1109
+ // OAuth flow
1110
+ if (client_id !== codeVal.clientID || client_secret !== codeVal.clientSecret) {
1111
+ throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
1112
+ }
1032
1113
  }
1033
- // encoded client_id, verify client_secret
1034
- if (client_secret !== this.opts.clientSecretVerifier) {
1114
+ else {
1115
+ if (!codeVal.isIdPFlow &&
1116
+ (sp.tenant !== ((_g = codeVal.requested) === null || _g === void 0 ? void 0 : _g.tenant) || sp.product !== ((_h = codeVal.requested) === null || _h === void 0 ? void 0 : _h.product))) {
1117
+ throw new error_1.JacksonError('Invalid tenant or product', 401);
1118
+ }
1119
+ // encoded client_id, verify client_secret
1120
+ if (client_secret !== this.opts.clientSecretVerifier) {
1121
+ throw new error_1.JacksonError('Invalid client_secret', 401);
1122
+ }
1123
+ }
1124
+ }
1125
+ else {
1126
+ if (client_secret !== this.opts.clientSecretVerifier && client_secret !== codeVal.clientSecret) {
1035
1127
  throw new error_1.JacksonError('Invalid client_secret', 401);
1036
1128
  }
1037
1129
  }
1038
1130
  }
1039
- else {
1040
- if (client_secret !== this.opts.clientSecretVerifier && client_secret !== codeVal.clientSecret) {
1041
- throw new error_1.JacksonError('Invalid client_secret', 401);
1131
+ else if (codeVal && codeVal.session) {
1132
+ throw new error_1.JacksonError('Please specify client_secret or code_verifier', 401);
1133
+ }
1134
+ // store details against a token
1135
+ const token = crypto_1.default.randomBytes(20).toString('hex');
1136
+ const tokenVal = Object.assign(Object.assign({}, codeVal.profile), { requested: codeVal.requested, login_type,
1137
+ protocol });
1138
+ const requestedOIDCFlow = !!((_j = codeVal.requested) === null || _j === void 0 ? void 0 : _j.oidc);
1139
+ const requestHasNonce = !!((_k = codeVal.requested) === null || _k === void 0 ? void 0 : _k.nonce);
1140
+ if (requestedOIDCFlow) {
1141
+ const { jwtSigningKeys, jwsAlg } = (_l = this.opts.openid) !== null && _l !== void 0 ? _l : {};
1142
+ if (!jwtSigningKeys || !(0, utils_1.isJWSKeyPairLoaded)(jwtSigningKeys)) {
1143
+ throw new error_1.JacksonError(utils_1.GENERIC_ERR_STRING, 500, 'JWT signing keys are not loaded');
1042
1144
  }
1145
+ let claims = requestHasNonce ? { nonce: codeVal.requested.nonce } : {};
1146
+ claims = Object.assign(Object.assign({}, claims), { id: codeVal.profile.claims.id, email: codeVal.profile.claims.email, firstName: codeVal.profile.claims.firstName, lastName: codeVal.profile.claims.lastName, roles: codeVal.profile.claims.roles, groups: codeVal.profile.claims.groups });
1147
+ const signingKey = yield (0, utils_1.loadJWSPrivateKey)(jwtSigningKeys.private, jwsAlg);
1148
+ const kid = yield (0, utils_1.computeKid)(jwtSigningKeys.public, jwsAlg);
1149
+ const id_token = yield new jose.SignJWT(claims)
1150
+ .setProtectedHeader({ alg: jwsAlg, kid })
1151
+ .setIssuedAt()
1152
+ .setIssuer(this.opts.externalUrl)
1153
+ .setSubject(codeVal.profile.claims.id)
1154
+ .setAudience(tokenVal.requested.client_id)
1155
+ .setExpirationTime(`${this.opts.db.ttl}s`) // identity token only really needs to be valid long enough for it to be verified by the client application.
1156
+ .sign(signingKey);
1157
+ tokenVal.id_token = id_token;
1158
+ tokenVal.claims.sub = codeVal.profile.claims.id;
1043
1159
  }
1044
- }
1045
- else if (codeVal && codeVal.session) {
1046
- throw new error_1.JacksonError('Please specify client_secret or code_verifier', 401);
1047
- }
1048
- // store details against a token
1049
- const token = crypto_1.default.randomBytes(20).toString('hex');
1050
- const tokenVal = Object.assign(Object.assign({}, codeVal.profile), { requested: codeVal.requested });
1051
- const requestedOIDCFlow = !!((_j = codeVal.requested) === null || _j === void 0 ? void 0 : _j.oidc);
1052
- const requestHasNonce = !!((_k = codeVal.requested) === null || _k === void 0 ? void 0 : _k.nonce);
1053
- if (requestedOIDCFlow) {
1054
- const { jwtSigningKeys, jwsAlg } = (_l = this.opts.openid) !== null && _l !== void 0 ? _l : {};
1055
- if (!jwtSigningKeys || !(0, utils_1.isJWSKeyPairLoaded)(jwtSigningKeys)) {
1056
- throw new error_1.JacksonError('JWT signing keys are not loaded', 500);
1160
+ const { hexKey, encVal } = encrypt(tokenVal);
1161
+ yield this.tokenStore.put(token, encVal);
1162
+ // delete the code
1163
+ try {
1164
+ yield this.codeStore.delete(code);
1165
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1057
1166
  }
1058
- let claims = requestHasNonce ? { nonce: codeVal.requested.nonce } : {};
1059
- claims = Object.assign(Object.assign({}, claims), { id: codeVal.profile.claims.id, email: codeVal.profile.claims.email, firstName: codeVal.profile.claims.firstName, lastName: codeVal.profile.claims.lastName, roles: codeVal.profile.claims.roles, groups: codeVal.profile.claims.groups });
1060
- const signingKey = yield (0, utils_1.loadJWSPrivateKey)(jwtSigningKeys.private, jwsAlg);
1061
- const kid = yield (0, utils_1.computeKid)(jwtSigningKeys.public, jwsAlg);
1062
- const id_token = yield new jose.SignJWT(claims)
1063
- .setProtectedHeader({ alg: jwsAlg, kid })
1064
- .setIssuedAt()
1065
- .setIssuer(this.opts.externalUrl)
1066
- .setSubject(codeVal.profile.claims.id)
1067
- .setAudience(tokenVal.requested.client_id)
1068
- .setExpirationTime(`${this.opts.db.ttl}s`) // identity token only really needs to be valid long enough for it to be verified by the client application.
1069
- .sign(signingKey);
1070
- tokenVal.id_token = id_token;
1071
- tokenVal.claims.sub = codeVal.profile.claims.id;
1072
- }
1073
- yield this.tokenStore.put(token, tokenVal);
1074
- // delete the code
1075
- try {
1076
- yield this.codeStore.delete(code);
1077
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1078
- }
1079
- catch (_err) {
1080
- // ignore error
1167
+ catch (_err) {
1168
+ // ignore error
1169
+ }
1170
+ const tokenResponse = {
1171
+ access_token: hexKey + '.' + token,
1172
+ token_type: 'bearer',
1173
+ expires_in: this.opts.db.ttl,
1174
+ };
1175
+ if (requestedOIDCFlow) {
1176
+ tokenResponse.id_token = tokenVal.id_token;
1177
+ }
1178
+ return tokenResponse;
1081
1179
  }
1082
- const tokenResponse = {
1083
- access_token: token,
1084
- token_type: 'bearer',
1085
- expires_in: this.opts.db.ttl,
1086
- };
1087
- if (requestedOIDCFlow) {
1088
- tokenResponse.id_token = tokenVal.id_token;
1180
+ catch (err) {
1181
+ metrics.increment('oauthTokenError', { protocol, login_type });
1182
+ throw err;
1089
1183
  }
1090
- return tokenResponse;
1091
1184
  });
1092
1185
  }
1093
1186
  /**
@@ -1139,9 +1232,18 @@ class OAuthController {
1139
1232
  */
1140
1233
  userInfo(token) {
1141
1234
  return __awaiter(this, void 0, void 0, function* () {
1142
- const rsp = yield this.tokenStore.get(token);
1235
+ const tokens = token.split('.');
1236
+ if (tokens.length !== 2) {
1237
+ throw new error_1.JacksonError('Invalid token', 403);
1238
+ }
1239
+ const encRsp = yield this.tokenStore.get(tokens[1]);
1240
+ if (!encRsp) {
1241
+ throw new error_1.JacksonError('Invalid token', 403);
1242
+ }
1243
+ const rsp = decrypt(encRsp, tokens[0]);
1143
1244
  metrics.increment('oauthUserInfo');
1144
1245
  if (!rsp || !rsp.claims) {
1246
+ metrics.increment('oauthUserInfoError', { protocol: rsp.protocol, login_type: rsp.login_type });
1145
1247
  throw new error_1.JacksonError('Invalid token', 403);
1146
1248
  }
1147
1249
  return Object.assign(Object.assign({}, rsp.claims), { requested: rsp.requested });