@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.
- package/dist/controller/analytics.d.ts +3 -1
- package/dist/controller/analytics.js +3 -2
- package/dist/controller/analytics.js.map +1 -1
- package/dist/controller/api.d.ts +1 -3
- package/dist/controller/api.js +5 -6
- package/dist/controller/api.js.map +1 -1
- package/dist/controller/connection/oidc.d.ts +2 -3
- package/dist/controller/connection/oidc.js +2 -26
- package/dist/controller/connection/oidc.js.map +1 -1
- package/dist/controller/connection/saml.d.ts +2 -3
- package/dist/controller/connection/saml.js +4 -28
- package/dist/controller/connection/saml.js.map +1 -1
- package/dist/controller/error.d.ts +2 -1
- package/dist/controller/error.js +4 -1
- package/dist/controller/error.js.map +1 -1
- package/dist/controller/oauth/oidc-client.js +1 -1
- package/dist/controller/oauth/oidc-client.js.map +1 -1
- package/dist/controller/oauth.d.ts +3 -0
- package/dist/controller/oauth.js +212 -110
- package/dist/controller/oauth.js.map +1 -1
- package/dist/controller/sso-handler.d.ts +2 -2
- package/dist/controller/sso-handler.js +73 -65
- package/dist/controller/sso-handler.js.map +1 -1
- package/dist/controller/utils.d.ts +1 -0
- package/dist/controller/utils.js +6 -1
- package/dist/controller/utils.js.map +1 -1
- package/dist/cron/lock.d.ts +4 -2
- package/dist/cron/lock.js +4 -3
- package/dist/cron/lock.js.map +1 -1
- package/dist/db/db.d.ts +5 -2
- package/dist/db/db.js +10 -9
- package/dist/db/db.js.map +1 -1
- package/dist/db/dynamoDb.d.ts +3 -1
- package/dist/db/dynamoDb.js +9 -9
- package/dist/db/dynamoDb.js.map +1 -1
- package/dist/db/mem.d.ts +3 -1
- package/dist/db/mem.js +1 -1
- package/dist/db/mem.js.map +1 -1
- package/dist/db/mongo.d.ts +7 -3
- package/dist/db/mongo.js +5 -4
- package/dist/db/mongo.js.map +1 -1
- package/dist/db/redis.d.ts +7 -3
- package/dist/db/redis.js +4 -3
- package/dist/db/redis.js.map +1 -1
- package/dist/db/sql/sql.d.ts +7 -3
- package/dist/db/sql/sql.js +4 -3
- package/dist/db/sql/sql.js.map +1 -1
- package/dist/directory-sync/batch-events/queue.d.ts +2 -2
- package/dist/directory-sync/batch-events/queue.js +13 -13
- package/dist/directory-sync/batch-events/queue.js.map +1 -1
- package/dist/directory-sync/index.d.ts +2 -2
- package/dist/directory-sync/index.js +2 -2
- package/dist/directory-sync/index.js.map +1 -1
- package/dist/directory-sync/non-scim/google/oauth.js +6 -6
- package/dist/directory-sync/non-scim/google/oauth.js.map +1 -1
- package/dist/directory-sync/non-scim/index.d.ts +2 -2
- package/dist/directory-sync/non-scim/index.js +4 -4
- package/dist/directory-sync/non-scim/index.js.map +1 -1
- package/dist/directory-sync/utils.d.ts +2 -2
- package/dist/directory-sync/utils.js +3 -3
- package/dist/directory-sync/utils.js.map +1 -1
- package/dist/ee/identity-federation/idp-login.js +3 -3
- package/dist/ee/identity-federation/idp-login.js.map +1 -1
- package/dist/ee/identity-federation/index.d.ts +2 -2
- package/dist/ee/identity-federation/sso.d.ts +2 -2
- package/dist/ee/identity-federation/sso.js +4 -4
- package/dist/ee/identity-federation/sso.js.map +1 -1
- package/dist/event/axios.d.ts +2 -2
- package/dist/event/axios.js +26 -22
- package/dist/event/axios.js.map +1 -1
- package/dist/event/index.d.ts +3 -2
- package/dist/event/index.js +2 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/webhook.d.ts +2 -2
- package/dist/event/webhook.js +5 -4
- package/dist/event/webhook.js.map +1 -1
- package/dist/index.js +24 -11
- package/dist/index.js.map +1 -1
- package/dist/opentelemetry/metrics.d.ts +6 -1
- package/dist/opentelemetry/metrics.js +26 -2
- package/dist/opentelemetry/metrics.js.map +1 -1
- package/dist/sso-traces/index.d.ts +2 -2
- package/dist/sso-traces/index.js +1 -1
- package/dist/sso-traces/index.js.map +1 -1
- package/dist/typings.d.ts +16 -7
- package/package.json +8 -8
- package/dist/ee/ory/ory.d.ts +0 -18
- package/dist/ee/ory/ory.js +0 -202
- package/dist/ee/ory/ory.js.map +0 -1
package/dist/controller/oauth.js
CHANGED
@@ -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.'
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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.'
|
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.'
|
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.'
|
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.'
|
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.'
|
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.'
|
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
|
-
|
902
|
-
|
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
|
-
|
987
|
-
|
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
|
-
|
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
|
-
|
1008
|
-
|
1068
|
+
const codes = code.split('.');
|
1069
|
+
if (codes.length !== 2) {
|
1070
|
+
throw new error_1.JacksonError('Invalid code', 403);
|
1009
1071
|
}
|
1010
|
-
|
1011
|
-
if (
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
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
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
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
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
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
|
-
|
1034
|
-
|
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
|
-
|
1041
|
-
|
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
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
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
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
const
|
1062
|
-
|
1063
|
-
|
1064
|
-
.
|
1065
|
-
|
1066
|
-
|
1067
|
-
.
|
1068
|
-
|
1069
|
-
|
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
|
-
|
1083
|
-
|
1084
|
-
|
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
|
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 });
|