@backstage/plugin-auth-backend 0.15.0-next.2 → 0.15.1-next.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/CHANGELOG.md +57 -0
- package/config.d.ts +3 -0
- package/dist/index.cjs.js +849 -379
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +102 -1
- package/package.json +12 -12
package/dist/index.cjs.js
CHANGED
|
@@ -22,7 +22,7 @@ var passportGoogleOauth20 = require('passport-google-oauth20');
|
|
|
22
22
|
var passportMicrosoft = require('passport-microsoft');
|
|
23
23
|
var pluginAuthNode = require('@backstage/plugin-auth-node');
|
|
24
24
|
var openidClient = require('openid-client');
|
|
25
|
-
var passportOktaOauth = require('passport-okta-oauth');
|
|
25
|
+
var passportOktaOauth = require('@davidzemon/passport-okta-oauth');
|
|
26
26
|
var passportOneloginOauth = require('passport-onelogin-oauth');
|
|
27
27
|
var passportSaml = require('passport-saml');
|
|
28
28
|
var catalogClient = require('@backstage/catalog-client');
|
|
@@ -96,10 +96,17 @@ class AtlassianStrategy extends OAuth2Strategy__default["default"] {
|
|
|
96
96
|
userProfile(accessToken, done) {
|
|
97
97
|
this._oauth2.get(this.profileURL, accessToken, (err, body) => {
|
|
98
98
|
if (err) {
|
|
99
|
-
return done(
|
|
99
|
+
return done(
|
|
100
|
+
new OAuth2Strategy.InternalOAuthError(
|
|
101
|
+
"Failed to fetch user profile",
|
|
102
|
+
err.statusCode
|
|
103
|
+
)
|
|
104
|
+
);
|
|
100
105
|
}
|
|
101
106
|
if (!body) {
|
|
102
|
-
return done(
|
|
107
|
+
return done(
|
|
108
|
+
new Error("Failed to fetch user profile, body cannot be empty")
|
|
109
|
+
);
|
|
103
110
|
}
|
|
104
111
|
try {
|
|
105
112
|
const json = typeof body !== "string" ? body.toString() : body;
|
|
@@ -125,14 +132,18 @@ class AtlassianStrategy extends OAuth2Strategy__default["default"] {
|
|
|
125
132
|
|
|
126
133
|
const readState = (stateString) => {
|
|
127
134
|
var _a, _b;
|
|
128
|
-
const state = Object.fromEntries(
|
|
135
|
+
const state = Object.fromEntries(
|
|
136
|
+
new URLSearchParams(Buffer.from(stateString, "hex").toString("utf-8"))
|
|
137
|
+
);
|
|
129
138
|
if (!state.nonce || !state.env || ((_a = state.nonce) == null ? void 0 : _a.length) === 0 || ((_b = state.env) == null ? void 0 : _b.length) === 0) {
|
|
130
139
|
throw Error(`Invalid state passed via request`);
|
|
131
140
|
}
|
|
132
141
|
return state;
|
|
133
142
|
};
|
|
134
143
|
const encodeState = (state) => {
|
|
135
|
-
const stateString = new URLSearchParams(
|
|
144
|
+
const stateString = new URLSearchParams(
|
|
145
|
+
pickBy__default["default"](state, (value) => value !== void 0)
|
|
146
|
+
).toString();
|
|
136
147
|
return Buffer.from(stateString, "utf-8").toString("hex");
|
|
137
148
|
};
|
|
138
149
|
const verifyNonce = (req, providerId) => {
|
|
@@ -212,7 +223,9 @@ class OAuthEnvironmentHandler {
|
|
|
212
223
|
}
|
|
213
224
|
const handler = this.handlers.get(env);
|
|
214
225
|
if (!handler) {
|
|
215
|
-
throw new errors.NotFoundError(
|
|
226
|
+
throw new errors.NotFoundError(
|
|
227
|
+
`No configuration available for the '${env}' environment of this provider.`
|
|
228
|
+
);
|
|
216
229
|
}
|
|
217
230
|
return handler;
|
|
218
231
|
}
|
|
@@ -340,7 +353,9 @@ class OAuthAdapter {
|
|
|
340
353
|
state.scope = scope;
|
|
341
354
|
}
|
|
342
355
|
const forwardReq = Object.assign(req, { scope, state });
|
|
343
|
-
const { url, status } = await this.handlers.start(
|
|
356
|
+
const { url, status } = await this.handlers.start(
|
|
357
|
+
forwardReq
|
|
358
|
+
);
|
|
344
359
|
res.statusCode = status || 302;
|
|
345
360
|
res.setHeader("Location", url);
|
|
346
361
|
res.setHeader("Content-Length", "0");
|
|
@@ -396,7 +411,9 @@ class OAuthAdapter {
|
|
|
396
411
|
throw new errors.AuthenticationError("Invalid X-Requested-With header");
|
|
397
412
|
}
|
|
398
413
|
if (!this.handlers.refresh) {
|
|
399
|
-
throw new errors.InputError(
|
|
414
|
+
throw new errors.InputError(
|
|
415
|
+
`Refresh token is not supported for provider ${this.options.providerId}`
|
|
416
|
+
);
|
|
400
417
|
}
|
|
401
418
|
try {
|
|
402
419
|
const refreshToken = req.cookies[`${this.options.providerId}-refresh-token`];
|
|
@@ -409,7 +426,9 @@ class OAuthAdapter {
|
|
|
409
426
|
}
|
|
410
427
|
const forwardReq = Object.assign(req, { scope, refreshToken });
|
|
411
428
|
const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
|
|
412
|
-
const backstageIdentity = await this.populateIdentity(
|
|
429
|
+
const backstageIdentity = await this.populateIdentity(
|
|
430
|
+
response.backstageIdentity
|
|
431
|
+
);
|
|
413
432
|
if (newRefreshToken && newRefreshToken !== refreshToken) {
|
|
414
433
|
this.setRefreshTokenCookie(res, newRefreshToken);
|
|
415
434
|
}
|
|
@@ -476,69 +495,89 @@ const executeRedirectStrategy = async (req, providerStrategy, options) => {
|
|
|
476
495
|
});
|
|
477
496
|
};
|
|
478
497
|
const executeFrameHandlerStrategy = async (req, providerStrategy) => {
|
|
479
|
-
return new Promise(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
498
|
+
return new Promise(
|
|
499
|
+
(resolve, reject) => {
|
|
500
|
+
const strategy = Object.create(providerStrategy);
|
|
501
|
+
strategy.success = (result, privateInfo) => {
|
|
502
|
+
resolve({ result, privateInfo });
|
|
503
|
+
};
|
|
504
|
+
strategy.fail = (info) => {
|
|
505
|
+
var _a;
|
|
506
|
+
reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
|
|
507
|
+
};
|
|
508
|
+
strategy.error = (error) => {
|
|
509
|
+
var _a;
|
|
510
|
+
let message = `Authentication failed, ${error.message}`;
|
|
511
|
+
if ((_a = error.oauthError) == null ? void 0 : _a.data) {
|
|
512
|
+
try {
|
|
513
|
+
const errorData = JSON.parse(error.oauthError.data);
|
|
514
|
+
if (errorData.message) {
|
|
515
|
+
message += ` - ${errorData.message}`;
|
|
516
|
+
}
|
|
517
|
+
} catch (parseError) {
|
|
518
|
+
message += ` - ${error.oauthError}`;
|
|
496
519
|
}
|
|
497
|
-
} catch (parseError) {
|
|
498
|
-
message += ` - ${error.oauthError}`;
|
|
499
520
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
521
|
+
reject(new Error(message));
|
|
522
|
+
};
|
|
523
|
+
strategy.redirect = () => {
|
|
524
|
+
reject(new Error("Unexpected redirect"));
|
|
525
|
+
};
|
|
526
|
+
strategy.authenticate(req, {});
|
|
527
|
+
}
|
|
528
|
+
);
|
|
508
529
|
};
|
|
509
530
|
const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
|
|
510
531
|
return new Promise((resolve, reject) => {
|
|
511
532
|
const anyStrategy = providerStrategy;
|
|
512
533
|
const OAuth2 = anyStrategy._oauth2.constructor;
|
|
513
|
-
const oauth2 = new OAuth2(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
534
|
+
const oauth2 = new OAuth2(
|
|
535
|
+
anyStrategy._oauth2._clientId,
|
|
536
|
+
anyStrategy._oauth2._clientSecret,
|
|
537
|
+
anyStrategy._oauth2._baseSite,
|
|
538
|
+
anyStrategy._oauth2._authorizeUrl,
|
|
539
|
+
anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl,
|
|
540
|
+
anyStrategy._oauth2._customHeaders
|
|
541
|
+
);
|
|
542
|
+
oauth2.getOAuthAccessToken(
|
|
543
|
+
refreshToken,
|
|
544
|
+
{
|
|
545
|
+
scope,
|
|
546
|
+
grant_type: "refresh_token"
|
|
547
|
+
},
|
|
548
|
+
(err, accessToken, newRefreshToken, params) => {
|
|
549
|
+
if (err) {
|
|
550
|
+
reject(new Error(`Failed to refresh access token ${err.toString()}`));
|
|
551
|
+
}
|
|
552
|
+
if (!accessToken) {
|
|
553
|
+
reject(
|
|
554
|
+
new Error(
|
|
555
|
+
`Failed to refresh access token, no access token received`
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
resolve({
|
|
560
|
+
accessToken,
|
|
561
|
+
refreshToken: newRefreshToken,
|
|
562
|
+
params
|
|
563
|
+
});
|
|
523
564
|
}
|
|
524
|
-
|
|
525
|
-
accessToken,
|
|
526
|
-
refreshToken: newRefreshToken,
|
|
527
|
-
params
|
|
528
|
-
});
|
|
529
|
-
});
|
|
565
|
+
);
|
|
530
566
|
});
|
|
531
567
|
};
|
|
532
568
|
const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
|
|
533
569
|
return new Promise((resolve, reject) => {
|
|
534
570
|
const anyStrategy = providerStrategy;
|
|
535
|
-
anyStrategy.userProfile(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
571
|
+
anyStrategy.userProfile(
|
|
572
|
+
accessToken,
|
|
573
|
+
(error, rawProfile) => {
|
|
574
|
+
if (error) {
|
|
575
|
+
reject(error);
|
|
576
|
+
} else {
|
|
577
|
+
resolve(rawProfile);
|
|
578
|
+
}
|
|
540
579
|
}
|
|
541
|
-
|
|
580
|
+
);
|
|
542
581
|
});
|
|
543
582
|
};
|
|
544
583
|
|
|
@@ -561,19 +600,22 @@ class AtlassianAuthProvider {
|
|
|
561
600
|
this.resolverContext = options.resolverContext;
|
|
562
601
|
this.authHandler = options.authHandler;
|
|
563
602
|
this.signInResolver = options.signInResolver;
|
|
564
|
-
this._strategy = new AtlassianStrategy(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
603
|
+
this._strategy = new AtlassianStrategy(
|
|
604
|
+
{
|
|
605
|
+
clientID: options.clientId,
|
|
606
|
+
clientSecret: options.clientSecret,
|
|
607
|
+
callbackURL: options.callbackUrl,
|
|
608
|
+
scope: options.scopes
|
|
609
|
+
},
|
|
610
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
611
|
+
done(void 0, {
|
|
612
|
+
fullProfile,
|
|
613
|
+
accessToken,
|
|
614
|
+
refreshToken,
|
|
615
|
+
params
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
);
|
|
577
619
|
}
|
|
578
620
|
async start(req) {
|
|
579
621
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -581,7 +623,10 @@ class AtlassianAuthProvider {
|
|
|
581
623
|
});
|
|
582
624
|
}
|
|
583
625
|
async handler(req) {
|
|
584
|
-
const { result } = await executeFrameHandlerStrategy(
|
|
626
|
+
const { result } = await executeFrameHandlerStrategy(
|
|
627
|
+
req,
|
|
628
|
+
this._strategy
|
|
629
|
+
);
|
|
585
630
|
return {
|
|
586
631
|
response: await this.handleResult(result),
|
|
587
632
|
refreshToken: result.refreshToken
|
|
@@ -599,16 +644,26 @@ class AtlassianAuthProvider {
|
|
|
599
644
|
profile
|
|
600
645
|
};
|
|
601
646
|
if (this.signInResolver) {
|
|
602
|
-
response.backstageIdentity = await this.signInResolver(
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
647
|
+
response.backstageIdentity = await this.signInResolver(
|
|
648
|
+
{
|
|
649
|
+
result,
|
|
650
|
+
profile
|
|
651
|
+
},
|
|
652
|
+
this.resolverContext
|
|
653
|
+
);
|
|
606
654
|
}
|
|
607
655
|
return response;
|
|
608
656
|
}
|
|
609
657
|
async refresh(req) {
|
|
610
|
-
const { accessToken, params, refreshToken } = await executeRefreshTokenStrategy(
|
|
611
|
-
|
|
658
|
+
const { accessToken, params, refreshToken } = await executeRefreshTokenStrategy(
|
|
659
|
+
this._strategy,
|
|
660
|
+
req.refreshToken,
|
|
661
|
+
req.scope
|
|
662
|
+
);
|
|
663
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
664
|
+
this._strategy,
|
|
665
|
+
accessToken
|
|
666
|
+
);
|
|
612
667
|
return {
|
|
613
668
|
response: await this.handleResult({
|
|
614
669
|
fullProfile,
|
|
@@ -664,22 +719,29 @@ class Auth0AuthProvider {
|
|
|
664
719
|
this.signInResolver = options.signInResolver;
|
|
665
720
|
this.authHandler = options.authHandler;
|
|
666
721
|
this.resolverContext = options.resolverContext;
|
|
667
|
-
this._strategy = new Auth0Strategy(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
722
|
+
this._strategy = new Auth0Strategy(
|
|
723
|
+
{
|
|
724
|
+
clientID: options.clientId,
|
|
725
|
+
clientSecret: options.clientSecret,
|
|
726
|
+
callbackURL: options.callbackUrl,
|
|
727
|
+
domain: options.domain,
|
|
728
|
+
passReqToCallback: false
|
|
729
|
+
},
|
|
730
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
731
|
+
done(
|
|
732
|
+
void 0,
|
|
733
|
+
{
|
|
734
|
+
fullProfile,
|
|
735
|
+
accessToken,
|
|
736
|
+
refreshToken,
|
|
737
|
+
params
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
refreshToken
|
|
741
|
+
}
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
);
|
|
683
745
|
}
|
|
684
746
|
async start(req) {
|
|
685
747
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -697,8 +759,15 @@ class Auth0AuthProvider {
|
|
|
697
759
|
};
|
|
698
760
|
}
|
|
699
761
|
async refresh(req) {
|
|
700
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
701
|
-
|
|
762
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
763
|
+
this._strategy,
|
|
764
|
+
req.refreshToken,
|
|
765
|
+
req.scope
|
|
766
|
+
);
|
|
767
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
768
|
+
this._strategy,
|
|
769
|
+
accessToken
|
|
770
|
+
);
|
|
702
771
|
return {
|
|
703
772
|
response: await this.handleResult({
|
|
704
773
|
fullProfile,
|
|
@@ -720,10 +789,13 @@ class Auth0AuthProvider {
|
|
|
720
789
|
profile
|
|
721
790
|
};
|
|
722
791
|
if (this.signInResolver) {
|
|
723
|
-
response.backstageIdentity = await this.signInResolver(
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
792
|
+
response.backstageIdentity = await this.signInResolver(
|
|
793
|
+
{
|
|
794
|
+
result,
|
|
795
|
+
profile
|
|
796
|
+
},
|
|
797
|
+
this.resolverContext
|
|
798
|
+
);
|
|
727
799
|
}
|
|
728
800
|
return response;
|
|
729
801
|
}
|
|
@@ -770,9 +842,16 @@ class AwsAlbAuthProvider {
|
|
|
770
842
|
if (optionalCacheKey) {
|
|
771
843
|
return crypto__namespace.createPublicKey(optionalCacheKey);
|
|
772
844
|
}
|
|
773
|
-
const keyText = await fetch__default["default"](
|
|
845
|
+
const keyText = await fetch__default["default"](
|
|
846
|
+
`https://public-keys.auth.elb.${encodeURIComponent(
|
|
847
|
+
this.region
|
|
848
|
+
)}.amazonaws.com/${encodeURIComponent(header.kid)}`
|
|
849
|
+
).then((response) => response.text());
|
|
774
850
|
const keyValue = crypto__namespace.createPublicKey(keyText);
|
|
775
|
-
this.keyCache.set(
|
|
851
|
+
this.keyCache.set(
|
|
852
|
+
header.kid,
|
|
853
|
+
keyValue.export({ format: "pem", type: "spki" })
|
|
854
|
+
);
|
|
776
855
|
return keyValue;
|
|
777
856
|
};
|
|
778
857
|
this.region = options.region;
|
|
@@ -791,7 +870,10 @@ class AwsAlbAuthProvider {
|
|
|
791
870
|
const response = await this.handleResult(result);
|
|
792
871
|
res.json(response);
|
|
793
872
|
} catch (e) {
|
|
794
|
-
throw new errors.AuthenticationError(
|
|
873
|
+
throw new errors.AuthenticationError(
|
|
874
|
+
"Exception occurred during AWS ALB token refresh",
|
|
875
|
+
e
|
|
876
|
+
);
|
|
795
877
|
}
|
|
796
878
|
}
|
|
797
879
|
start() {
|
|
@@ -801,10 +883,14 @@ class AwsAlbAuthProvider {
|
|
|
801
883
|
const jwt = req.header(ALB_JWT_HEADER);
|
|
802
884
|
const accessToken = req.header(ALB_ACCESS_TOKEN_HEADER);
|
|
803
885
|
if (jwt === void 0) {
|
|
804
|
-
throw new errors.AuthenticationError(
|
|
886
|
+
throw new errors.AuthenticationError(
|
|
887
|
+
`Missing ALB OIDC header: ${ALB_JWT_HEADER}`
|
|
888
|
+
);
|
|
805
889
|
}
|
|
806
890
|
if (accessToken === void 0) {
|
|
807
|
-
throw new errors.AuthenticationError(
|
|
891
|
+
throw new errors.AuthenticationError(
|
|
892
|
+
`Missing ALB OIDC header: ${ALB_ACCESS_TOKEN_HEADER}`
|
|
893
|
+
);
|
|
808
894
|
}
|
|
809
895
|
try {
|
|
810
896
|
const verifyResult = await jose.jwtVerify(jwt, this.getKey);
|
|
@@ -835,10 +921,13 @@ class AwsAlbAuthProvider {
|
|
|
835
921
|
}
|
|
836
922
|
async handleResult(result) {
|
|
837
923
|
const { profile } = await this.authHandler(result, this.resolverContext);
|
|
838
|
-
const backstageIdentity = await this.signInResolver(
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
924
|
+
const backstageIdentity = await this.signInResolver(
|
|
925
|
+
{
|
|
926
|
+
result,
|
|
927
|
+
profile
|
|
928
|
+
},
|
|
929
|
+
this.resolverContext
|
|
930
|
+
);
|
|
842
931
|
return {
|
|
843
932
|
providerInfo: {
|
|
844
933
|
accessToken: result.accessToken,
|
|
@@ -855,7 +944,9 @@ const awsAlb = createAuthProviderIntegration({
|
|
|
855
944
|
const region = config.getString("region");
|
|
856
945
|
const issuer = config.getOptionalString("iss");
|
|
857
946
|
if ((options == null ? void 0 : options.signIn.resolver) === void 0) {
|
|
858
|
-
throw new Error(
|
|
947
|
+
throw new Error(
|
|
948
|
+
"SignInResolver is required to use this authentication provider"
|
|
949
|
+
);
|
|
859
950
|
}
|
|
860
951
|
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
861
952
|
profile: makeProfileInfo(fullProfile)
|
|
@@ -876,21 +967,28 @@ class BitbucketAuthProvider {
|
|
|
876
967
|
this.signInResolver = options.signInResolver;
|
|
877
968
|
this.authHandler = options.authHandler;
|
|
878
969
|
this.resolverContext = options.resolverContext;
|
|
879
|
-
this._strategy = new passportBitbucketOauth2.Strategy(
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
970
|
+
this._strategy = new passportBitbucketOauth2.Strategy(
|
|
971
|
+
{
|
|
972
|
+
clientID: options.clientId,
|
|
973
|
+
clientSecret: options.clientSecret,
|
|
974
|
+
callbackURL: options.callbackUrl,
|
|
975
|
+
passReqToCallback: false
|
|
976
|
+
},
|
|
977
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
978
|
+
done(
|
|
979
|
+
void 0,
|
|
980
|
+
{
|
|
981
|
+
fullProfile,
|
|
982
|
+
params,
|
|
983
|
+
accessToken,
|
|
984
|
+
refreshToken
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
refreshToken
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
);
|
|
894
992
|
}
|
|
895
993
|
async start(req) {
|
|
896
994
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -908,8 +1006,15 @@ class BitbucketAuthProvider {
|
|
|
908
1006
|
};
|
|
909
1007
|
}
|
|
910
1008
|
async refresh(req) {
|
|
911
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
912
|
-
|
|
1009
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1010
|
+
this._strategy,
|
|
1011
|
+
req.refreshToken,
|
|
1012
|
+
req.scope
|
|
1013
|
+
);
|
|
1014
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1015
|
+
this._strategy,
|
|
1016
|
+
accessToken
|
|
1017
|
+
);
|
|
913
1018
|
return {
|
|
914
1019
|
response: await this.handleResult({
|
|
915
1020
|
fullProfile,
|
|
@@ -932,10 +1037,13 @@ class BitbucketAuthProvider {
|
|
|
932
1037
|
profile
|
|
933
1038
|
};
|
|
934
1039
|
if (this.signInResolver) {
|
|
935
|
-
response.backstageIdentity = await this.signInResolver(
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1040
|
+
response.backstageIdentity = await this.signInResolver(
|
|
1041
|
+
{
|
|
1042
|
+
result,
|
|
1043
|
+
profile
|
|
1044
|
+
},
|
|
1045
|
+
this.resolverContext
|
|
1046
|
+
);
|
|
939
1047
|
}
|
|
940
1048
|
return response;
|
|
941
1049
|
}
|
|
@@ -995,13 +1103,172 @@ const bitbucket = createAuthProviderIntegration({
|
|
|
995
1103
|
}
|
|
996
1104
|
});
|
|
997
1105
|
|
|
1106
|
+
const commonByEmailLocalPartResolver = async (info, ctx) => {
|
|
1107
|
+
const { profile } = info;
|
|
1108
|
+
if (!profile.email) {
|
|
1109
|
+
throw new Error("Login failed, user profile does not contain an email");
|
|
1110
|
+
}
|
|
1111
|
+
const [localPart] = profile.email.split("@");
|
|
1112
|
+
return ctx.signInWithCatalogUser({
|
|
1113
|
+
entityRef: { name: localPart }
|
|
1114
|
+
});
|
|
1115
|
+
};
|
|
1116
|
+
const commonByEmailResolver = async (info, ctx) => {
|
|
1117
|
+
const { profile } = info;
|
|
1118
|
+
if (!profile.email) {
|
|
1119
|
+
throw new Error("Login failed, user profile does not contain an email");
|
|
1120
|
+
}
|
|
1121
|
+
return ctx.signInWithCatalogUser({
|
|
1122
|
+
filter: {
|
|
1123
|
+
"spec.profile.email": profile.email
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
const CF_JWT_HEADER = "cf-access-jwt-assertion";
|
|
1129
|
+
const COOKIE_AUTH_NAME = "CF_Authorization";
|
|
1130
|
+
const CACHE_PREFIX = "providers/cloudflare-access/profile-v1";
|
|
1131
|
+
class CloudflareAccessAuthProvider {
|
|
1132
|
+
constructor(options) {
|
|
1133
|
+
this.teamName = options.teamName;
|
|
1134
|
+
this.authHandler = options.authHandler;
|
|
1135
|
+
this.signInResolver = options.signInResolver;
|
|
1136
|
+
this.resolverContext = options.resolverContext;
|
|
1137
|
+
this.jwtKeySet = jose.createRemoteJWKSet(
|
|
1138
|
+
new URL(
|
|
1139
|
+
`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/certs`
|
|
1140
|
+
)
|
|
1141
|
+
);
|
|
1142
|
+
this.cache = options.cache;
|
|
1143
|
+
}
|
|
1144
|
+
frameHandler() {
|
|
1145
|
+
return Promise.resolve();
|
|
1146
|
+
}
|
|
1147
|
+
async refresh(req, res) {
|
|
1148
|
+
const result = await this.getResult(req);
|
|
1149
|
+
const response = await this.handleResult(result);
|
|
1150
|
+
res.json(response);
|
|
1151
|
+
}
|
|
1152
|
+
start() {
|
|
1153
|
+
return Promise.resolve();
|
|
1154
|
+
}
|
|
1155
|
+
async getIdentityProfile(jwt) {
|
|
1156
|
+
const headers = new fetch.Headers();
|
|
1157
|
+
headers.set(CF_JWT_HEADER, jwt);
|
|
1158
|
+
headers.set("cookie", `${COOKIE_AUTH_NAME}=${jwt}`);
|
|
1159
|
+
try {
|
|
1160
|
+
const res = await fetch__default["default"](
|
|
1161
|
+
`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`,
|
|
1162
|
+
{ headers }
|
|
1163
|
+
);
|
|
1164
|
+
if (!res.ok) {
|
|
1165
|
+
throw errors.ResponseError.fromResponse(res);
|
|
1166
|
+
}
|
|
1167
|
+
const cfIdentity = await res.json();
|
|
1168
|
+
return cfIdentity;
|
|
1169
|
+
} catch (err) {
|
|
1170
|
+
throw new errors.ForwardedError("getIdentityProfile failed", err);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async getResult(req) {
|
|
1174
|
+
var _a, _b;
|
|
1175
|
+
let jwt = req.header(CF_JWT_HEADER);
|
|
1176
|
+
if (!jwt) {
|
|
1177
|
+
jwt = req.cookies.CF_Authorization;
|
|
1178
|
+
}
|
|
1179
|
+
if (!jwt) {
|
|
1180
|
+
throw new errors.AuthenticationError(
|
|
1181
|
+
`Missing ${CF_JWT_HEADER} from Cloudflare Access`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
const verifyResult = await jose.jwtVerify(jwt, this.jwtKeySet, {
|
|
1185
|
+
issuer: `https://${this.teamName}.cloudflareaccess.com`
|
|
1186
|
+
});
|
|
1187
|
+
const sub = verifyResult.payload.sub;
|
|
1188
|
+
const cfAccessResultStr = await ((_a = this.cache) == null ? void 0 : _a.get(`${CACHE_PREFIX}/${sub}`));
|
|
1189
|
+
if (typeof cfAccessResultStr === "string") {
|
|
1190
|
+
return JSON.parse(cfAccessResultStr);
|
|
1191
|
+
}
|
|
1192
|
+
const claims = verifyResult.payload;
|
|
1193
|
+
try {
|
|
1194
|
+
const cfIdentity = await this.getIdentityProfile(jwt);
|
|
1195
|
+
const cfAccessResult = {
|
|
1196
|
+
claims,
|
|
1197
|
+
cfIdentity,
|
|
1198
|
+
expiresInSeconds: claims.exp - claims.iat
|
|
1199
|
+
};
|
|
1200
|
+
(_b = this.cache) == null ? void 0 : _b.set(`${CACHE_PREFIX}/${sub}`, JSON.stringify(cfAccessResult));
|
|
1201
|
+
return cfAccessResult;
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
throw new errors.ForwardedError(
|
|
1204
|
+
"Failed to populate access identity information",
|
|
1205
|
+
err
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
async handleResult(result) {
|
|
1210
|
+
const { profile } = await this.authHandler(result, this.resolverContext);
|
|
1211
|
+
const backstageIdentity = await this.signInResolver(
|
|
1212
|
+
{
|
|
1213
|
+
result,
|
|
1214
|
+
profile
|
|
1215
|
+
},
|
|
1216
|
+
this.resolverContext
|
|
1217
|
+
);
|
|
1218
|
+
return {
|
|
1219
|
+
providerInfo: {
|
|
1220
|
+
expiresInSeconds: result.expiresInSeconds,
|
|
1221
|
+
claims: result.claims,
|
|
1222
|
+
cfAccessIdentityProfile: result.cfIdentity
|
|
1223
|
+
},
|
|
1224
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
|
|
1225
|
+
profile
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const cfAccess = createAuthProviderIntegration({
|
|
1230
|
+
create(options) {
|
|
1231
|
+
return ({ config, resolverContext }) => {
|
|
1232
|
+
const teamName = config.getString("teamName");
|
|
1233
|
+
if (!options.signIn.resolver) {
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
"SignInResolver is required to use this authentication provider"
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ claims, cfIdentity }) => {
|
|
1239
|
+
return {
|
|
1240
|
+
profile: {
|
|
1241
|
+
email: claims.email,
|
|
1242
|
+
displayName: cfIdentity.name
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
};
|
|
1246
|
+
return new CloudflareAccessAuthProvider({
|
|
1247
|
+
teamName,
|
|
1248
|
+
signInResolver: options == null ? void 0 : options.signIn.resolver,
|
|
1249
|
+
authHandler,
|
|
1250
|
+
resolverContext,
|
|
1251
|
+
...options.cache && { cache: options.cache }
|
|
1252
|
+
});
|
|
1253
|
+
};
|
|
1254
|
+
},
|
|
1255
|
+
resolvers: {
|
|
1256
|
+
emailMatchingUserEntityProfileEmail: () => commonByEmailResolver
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
|
|
998
1260
|
const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
|
|
999
1261
|
|
|
1000
1262
|
function createTokenValidator(audience, mockClient) {
|
|
1001
1263
|
const client = mockClient != null ? mockClient : new googleAuthLibrary.OAuth2Client();
|
|
1002
1264
|
return async function tokenValidator(token) {
|
|
1003
1265
|
const response = await client.getIapPublicKeys();
|
|
1004
|
-
const ticket = await client.verifySignedJwtWithCertsAsync(
|
|
1266
|
+
const ticket = await client.verifySignedJwtWithCertsAsync(
|
|
1267
|
+
token,
|
|
1268
|
+
response.pubkeys,
|
|
1269
|
+
audience,
|
|
1270
|
+
["https://cloud.google.com/iap"]
|
|
1271
|
+
);
|
|
1005
1272
|
const payload = ticket.getPayload();
|
|
1006
1273
|
if (!payload) {
|
|
1007
1274
|
throw new TypeError("Token had no payload");
|
|
@@ -1011,7 +1278,9 @@ function createTokenValidator(audience, mockClient) {
|
|
|
1011
1278
|
}
|
|
1012
1279
|
async function parseRequestToken(jwtToken, tokenValidator) {
|
|
1013
1280
|
if (typeof jwtToken !== "string" || !jwtToken) {
|
|
1014
|
-
throw new errors.AuthenticationError(
|
|
1281
|
+
throw new errors.AuthenticationError(
|
|
1282
|
+
`Missing Google IAP header: ${IAP_JWT_HEADER}`
|
|
1283
|
+
);
|
|
1015
1284
|
}
|
|
1016
1285
|
let payload;
|
|
1017
1286
|
try {
|
|
@@ -1020,7 +1289,9 @@ async function parseRequestToken(jwtToken, tokenValidator) {
|
|
|
1020
1289
|
throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
|
|
1021
1290
|
}
|
|
1022
1291
|
if (!payload.sub || !payload.email) {
|
|
1023
|
-
throw new errors.AuthenticationError(
|
|
1292
|
+
throw new errors.AuthenticationError(
|
|
1293
|
+
"Google IAP token payload is missing sub and/or email claim"
|
|
1294
|
+
);
|
|
1024
1295
|
}
|
|
1025
1296
|
return {
|
|
1026
1297
|
iapToken: {
|
|
@@ -1046,9 +1317,15 @@ class GcpIapProvider {
|
|
|
1046
1317
|
async frameHandler() {
|
|
1047
1318
|
}
|
|
1048
1319
|
async refresh(req, res) {
|
|
1049
|
-
const result = await parseRequestToken(
|
|
1320
|
+
const result = await parseRequestToken(
|
|
1321
|
+
req.header(IAP_JWT_HEADER),
|
|
1322
|
+
this.tokenValidator
|
|
1323
|
+
);
|
|
1050
1324
|
const { profile } = await this.authHandler(result, this.resolverContext);
|
|
1051
|
-
const backstageIdentity = await this.signInResolver(
|
|
1325
|
+
const backstageIdentity = await this.signInResolver(
|
|
1326
|
+
{ profile, result },
|
|
1327
|
+
this.resolverContext
|
|
1328
|
+
);
|
|
1052
1329
|
const response = {
|
|
1053
1330
|
providerInfo: { iapToken: result.iapToken },
|
|
1054
1331
|
profile,
|
|
@@ -1083,16 +1360,19 @@ class GithubAuthProvider {
|
|
|
1083
1360
|
this.authHandler = options.authHandler;
|
|
1084
1361
|
this.stateEncoder = options.stateEncoder;
|
|
1085
1362
|
this.resolverContext = options.resolverContext;
|
|
1086
|
-
this._strategy = new passportGithub2.Strategy(
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1363
|
+
this._strategy = new passportGithub2.Strategy(
|
|
1364
|
+
{
|
|
1365
|
+
clientID: options.clientId,
|
|
1366
|
+
clientSecret: options.clientSecret,
|
|
1367
|
+
callbackURL: options.callbackUrl,
|
|
1368
|
+
tokenURL: options.tokenUrl,
|
|
1369
|
+
userProfileURL: options.userProfileUrl,
|
|
1370
|
+
authorizationURL: options.authorizationUrl
|
|
1371
|
+
},
|
|
1372
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
1373
|
+
done(void 0, { fullProfile, params, accessToken }, { refreshToken });
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1096
1376
|
}
|
|
1097
1377
|
async start(req) {
|
|
1098
1378
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -1115,7 +1395,10 @@ class GithubAuthProvider {
|
|
|
1115
1395
|
const { scope, refreshToken } = req;
|
|
1116
1396
|
if (refreshToken == null ? void 0 : refreshToken.startsWith(ACCESS_TOKEN_PREFIX)) {
|
|
1117
1397
|
const accessToken = refreshToken.slice(ACCESS_TOKEN_PREFIX.length);
|
|
1118
|
-
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1398
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1399
|
+
this._strategy,
|
|
1400
|
+
accessToken
|
|
1401
|
+
).catch((error) => {
|
|
1119
1402
|
var _a;
|
|
1120
1403
|
if (((_a = error.oauthError) == null ? void 0 : _a.statusCode) === 401) {
|
|
1121
1404
|
throw new Error("Invalid access token");
|
|
@@ -1131,10 +1414,17 @@ class GithubAuthProvider {
|
|
|
1131
1414
|
refreshToken
|
|
1132
1415
|
};
|
|
1133
1416
|
}
|
|
1134
|
-
const result = await executeRefreshTokenStrategy(
|
|
1417
|
+
const result = await executeRefreshTokenStrategy(
|
|
1418
|
+
this._strategy,
|
|
1419
|
+
refreshToken,
|
|
1420
|
+
scope
|
|
1421
|
+
);
|
|
1135
1422
|
return {
|
|
1136
1423
|
response: await this.handleResult({
|
|
1137
|
-
fullProfile: await executeFetchUserProfileStrategy(
|
|
1424
|
+
fullProfile: await executeFetchUserProfileStrategy(
|
|
1425
|
+
this._strategy,
|
|
1426
|
+
result.accessToken
|
|
1427
|
+
),
|
|
1138
1428
|
params: { ...result.params, scope },
|
|
1139
1429
|
accessToken: result.accessToken
|
|
1140
1430
|
}),
|
|
@@ -1147,12 +1437,18 @@ class GithubAuthProvider {
|
|
|
1147
1437
|
let expiresInSeconds = expiresInStr === void 0 ? void 0 : Number(expiresInStr);
|
|
1148
1438
|
let backstageIdentity = void 0;
|
|
1149
1439
|
if (this.signInResolver) {
|
|
1150
|
-
backstageIdentity = await this.signInResolver(
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1440
|
+
backstageIdentity = await this.signInResolver(
|
|
1441
|
+
{
|
|
1442
|
+
result,
|
|
1443
|
+
profile
|
|
1444
|
+
},
|
|
1445
|
+
this.resolverContext
|
|
1446
|
+
);
|
|
1154
1447
|
if (expiresInSeconds) {
|
|
1155
|
-
expiresInSeconds = Math.min(
|
|
1448
|
+
expiresInSeconds = Math.min(
|
|
1449
|
+
expiresInSeconds,
|
|
1450
|
+
BACKSTAGE_SESSION_EXPIRATION
|
|
1451
|
+
);
|
|
1156
1452
|
} else {
|
|
1157
1453
|
expiresInSeconds = BACKSTAGE_SESSION_EXPIRATION;
|
|
1158
1454
|
}
|
|
@@ -1230,16 +1526,23 @@ class GitlabAuthProvider {
|
|
|
1230
1526
|
this.resolverContext = options.resolverContext;
|
|
1231
1527
|
this.authHandler = options.authHandler;
|
|
1232
1528
|
this.signInResolver = options.signInResolver;
|
|
1233
|
-
this._strategy = new passportGitlab2.Strategy(
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1529
|
+
this._strategy = new passportGitlab2.Strategy(
|
|
1530
|
+
{
|
|
1531
|
+
clientID: options.clientId,
|
|
1532
|
+
clientSecret: options.clientSecret,
|
|
1533
|
+
callbackURL: options.callbackUrl,
|
|
1534
|
+
baseURL: options.baseUrl
|
|
1535
|
+
},
|
|
1536
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
1537
|
+
done(
|
|
1538
|
+
void 0,
|
|
1539
|
+
{ fullProfile, params, accessToken },
|
|
1540
|
+
{
|
|
1541
|
+
refreshToken
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
);
|
|
1243
1546
|
}
|
|
1244
1547
|
async start(req) {
|
|
1245
1548
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -1255,8 +1558,15 @@ class GitlabAuthProvider {
|
|
|
1255
1558
|
};
|
|
1256
1559
|
}
|
|
1257
1560
|
async refresh(req) {
|
|
1258
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1259
|
-
|
|
1561
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1562
|
+
this._strategy,
|
|
1563
|
+
req.refreshToken,
|
|
1564
|
+
req.scope
|
|
1565
|
+
);
|
|
1566
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1567
|
+
this._strategy,
|
|
1568
|
+
accessToken
|
|
1569
|
+
);
|
|
1260
1570
|
return {
|
|
1261
1571
|
response: await this.handleResult({
|
|
1262
1572
|
fullProfile,
|
|
@@ -1278,10 +1588,13 @@ class GitlabAuthProvider {
|
|
|
1278
1588
|
profile
|
|
1279
1589
|
};
|
|
1280
1590
|
if (this.signInResolver) {
|
|
1281
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1591
|
+
response.backstageIdentity = await this.signInResolver(
|
|
1592
|
+
{
|
|
1593
|
+
result,
|
|
1594
|
+
profile
|
|
1595
|
+
},
|
|
1596
|
+
this.resolverContext
|
|
1597
|
+
);
|
|
1285
1598
|
}
|
|
1286
1599
|
return response;
|
|
1287
1600
|
}
|
|
@@ -1314,48 +1627,33 @@ const gitlab = createAuthProviderIntegration({
|
|
|
1314
1627
|
}
|
|
1315
1628
|
});
|
|
1316
1629
|
|
|
1317
|
-
const commonByEmailLocalPartResolver = async (info, ctx) => {
|
|
1318
|
-
const { profile } = info;
|
|
1319
|
-
if (!profile.email) {
|
|
1320
|
-
throw new Error("Login failed, user profile does not contain an email");
|
|
1321
|
-
}
|
|
1322
|
-
const [localPart] = profile.email.split("@");
|
|
1323
|
-
return ctx.signInWithCatalogUser({
|
|
1324
|
-
entityRef: { name: localPart }
|
|
1325
|
-
});
|
|
1326
|
-
};
|
|
1327
|
-
const commonByEmailResolver = async (info, ctx) => {
|
|
1328
|
-
const { profile } = info;
|
|
1329
|
-
if (!profile.email) {
|
|
1330
|
-
throw new Error("Login failed, user profile does not contain an email");
|
|
1331
|
-
}
|
|
1332
|
-
return ctx.signInWithCatalogUser({
|
|
1333
|
-
filter: {
|
|
1334
|
-
"spec.profile.email": profile.email
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
1630
|
class GoogleAuthProvider {
|
|
1340
1631
|
constructor(options) {
|
|
1341
1632
|
this.authHandler = options.authHandler;
|
|
1342
1633
|
this.signInResolver = options.signInResolver;
|
|
1343
1634
|
this.resolverContext = options.resolverContext;
|
|
1344
|
-
this.strategy = new passportGoogleOauth20.Strategy(
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1635
|
+
this.strategy = new passportGoogleOauth20.Strategy(
|
|
1636
|
+
{
|
|
1637
|
+
clientID: options.clientId,
|
|
1638
|
+
clientSecret: options.clientSecret,
|
|
1639
|
+
callbackURL: options.callbackUrl,
|
|
1640
|
+
passReqToCallback: false
|
|
1641
|
+
},
|
|
1642
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
1643
|
+
done(
|
|
1644
|
+
void 0,
|
|
1645
|
+
{
|
|
1646
|
+
fullProfile,
|
|
1647
|
+
params,
|
|
1648
|
+
accessToken,
|
|
1649
|
+
refreshToken
|
|
1650
|
+
},
|
|
1651
|
+
{
|
|
1652
|
+
refreshToken
|
|
1653
|
+
}
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
);
|
|
1359
1657
|
}
|
|
1360
1658
|
async start(req) {
|
|
1361
1659
|
return await executeRedirectStrategy(req, this.strategy, {
|
|
@@ -1373,8 +1671,15 @@ class GoogleAuthProvider {
|
|
|
1373
1671
|
};
|
|
1374
1672
|
}
|
|
1375
1673
|
async refresh(req) {
|
|
1376
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1377
|
-
|
|
1674
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1675
|
+
this.strategy,
|
|
1676
|
+
req.refreshToken,
|
|
1677
|
+
req.scope
|
|
1678
|
+
);
|
|
1679
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1680
|
+
this.strategy,
|
|
1681
|
+
accessToken
|
|
1682
|
+
);
|
|
1378
1683
|
return {
|
|
1379
1684
|
response: await this.handleResult({
|
|
1380
1685
|
fullProfile,
|
|
@@ -1396,10 +1701,13 @@ class GoogleAuthProvider {
|
|
|
1396
1701
|
profile
|
|
1397
1702
|
};
|
|
1398
1703
|
if (this.signInResolver) {
|
|
1399
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1704
|
+
response.backstageIdentity = await this.signInResolver(
|
|
1705
|
+
{
|
|
1706
|
+
result,
|
|
1707
|
+
profile
|
|
1708
|
+
},
|
|
1709
|
+
this.resolverContext
|
|
1710
|
+
);
|
|
1403
1711
|
}
|
|
1404
1712
|
return response;
|
|
1405
1713
|
}
|
|
@@ -1454,16 +1762,19 @@ class MicrosoftAuthProvider {
|
|
|
1454
1762
|
this.authHandler = options.authHandler;
|
|
1455
1763
|
this.logger = options.logger;
|
|
1456
1764
|
this.resolverContext = options.resolverContext;
|
|
1457
|
-
this._strategy = new passportMicrosoft.Strategy(
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1765
|
+
this._strategy = new passportMicrosoft.Strategy(
|
|
1766
|
+
{
|
|
1767
|
+
clientID: options.clientId,
|
|
1768
|
+
clientSecret: options.clientSecret,
|
|
1769
|
+
callbackURL: options.callbackUrl,
|
|
1770
|
+
authorizationURL: options.authorizationUrl,
|
|
1771
|
+
tokenURL: options.tokenUrl,
|
|
1772
|
+
passReqToCallback: false
|
|
1773
|
+
},
|
|
1774
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
1775
|
+
done(void 0, { fullProfile, accessToken, params }, { refreshToken });
|
|
1776
|
+
}
|
|
1777
|
+
);
|
|
1467
1778
|
}
|
|
1468
1779
|
async start(req) {
|
|
1469
1780
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -1479,8 +1790,15 @@ class MicrosoftAuthProvider {
|
|
|
1479
1790
|
};
|
|
1480
1791
|
}
|
|
1481
1792
|
async refresh(req) {
|
|
1482
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1483
|
-
|
|
1793
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1794
|
+
this._strategy,
|
|
1795
|
+
req.refreshToken,
|
|
1796
|
+
req.scope
|
|
1797
|
+
);
|
|
1798
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1799
|
+
this._strategy,
|
|
1800
|
+
accessToken
|
|
1801
|
+
);
|
|
1484
1802
|
return {
|
|
1485
1803
|
response: await this.handleResult({
|
|
1486
1804
|
fullProfile,
|
|
@@ -1504,24 +1822,32 @@ class MicrosoftAuthProvider {
|
|
|
1504
1822
|
profile
|
|
1505
1823
|
};
|
|
1506
1824
|
if (this.signInResolver) {
|
|
1507
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1825
|
+
response.backstageIdentity = await this.signInResolver(
|
|
1826
|
+
{
|
|
1827
|
+
result,
|
|
1828
|
+
profile
|
|
1829
|
+
},
|
|
1830
|
+
this.resolverContext
|
|
1831
|
+
);
|
|
1511
1832
|
}
|
|
1512
1833
|
return response;
|
|
1513
1834
|
}
|
|
1514
1835
|
async getUserPhoto(accessToken) {
|
|
1515
1836
|
try {
|
|
1516
|
-
const res = await fetch__default["default"](
|
|
1517
|
-
|
|
1518
|
-
|
|
1837
|
+
const res = await fetch__default["default"](
|
|
1838
|
+
"https://graph.microsoft.com/v1.0/me/photos/48x48/$value",
|
|
1839
|
+
{
|
|
1840
|
+
headers: {
|
|
1841
|
+
Authorization: `Bearer ${accessToken}`
|
|
1842
|
+
}
|
|
1519
1843
|
}
|
|
1520
|
-
|
|
1844
|
+
);
|
|
1521
1845
|
const data = await res.buffer();
|
|
1522
1846
|
return `data:image/jpeg;base64,${data.toString("base64")}`;
|
|
1523
1847
|
} catch (error) {
|
|
1524
|
-
this.logger.warn(
|
|
1848
|
+
this.logger.warn(
|
|
1849
|
+
`Could not retrieve user profile photo from Microsoft Graph API: ${error}`
|
|
1850
|
+
);
|
|
1525
1851
|
return void 0;
|
|
1526
1852
|
}
|
|
1527
1853
|
}
|
|
@@ -1583,27 +1909,37 @@ class OAuth2AuthProvider {
|
|
|
1583
1909
|
this.authHandler = options.authHandler;
|
|
1584
1910
|
this.resolverContext = options.resolverContext;
|
|
1585
1911
|
this.disableRefresh = (_a = options.disableRefresh) != null ? _a : false;
|
|
1586
|
-
this._strategy = new OAuth2Strategy.Strategy(
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1912
|
+
this._strategy = new OAuth2Strategy.Strategy(
|
|
1913
|
+
{
|
|
1914
|
+
clientID: options.clientId,
|
|
1915
|
+
clientSecret: options.clientSecret,
|
|
1916
|
+
callbackURL: options.callbackUrl,
|
|
1917
|
+
authorizationURL: options.authorizationUrl,
|
|
1918
|
+
tokenURL: options.tokenUrl,
|
|
1919
|
+
passReqToCallback: false,
|
|
1920
|
+
scope: options.scope,
|
|
1921
|
+
customHeaders: options.includeBasicAuth ? {
|
|
1922
|
+
Authorization: `Basic ${this.encodeClientCredentials(
|
|
1923
|
+
options.clientId,
|
|
1924
|
+
options.clientSecret
|
|
1925
|
+
)}`
|
|
1926
|
+
} : void 0
|
|
1927
|
+
},
|
|
1928
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
1929
|
+
done(
|
|
1930
|
+
void 0,
|
|
1931
|
+
{
|
|
1932
|
+
fullProfile,
|
|
1933
|
+
accessToken,
|
|
1934
|
+
refreshToken,
|
|
1935
|
+
params
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
refreshToken
|
|
1939
|
+
}
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
);
|
|
1607
1943
|
}
|
|
1608
1944
|
async start(req) {
|
|
1609
1945
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -1624,9 +1960,16 @@ class OAuth2AuthProvider {
|
|
|
1624
1960
|
if (this.disableRefresh) {
|
|
1625
1961
|
throw new errors.InputError("Session refreshes have been disabled");
|
|
1626
1962
|
}
|
|
1627
|
-
const refreshTokenResponse = await executeRefreshTokenStrategy(
|
|
1963
|
+
const refreshTokenResponse = await executeRefreshTokenStrategy(
|
|
1964
|
+
this._strategy,
|
|
1965
|
+
req.refreshToken,
|
|
1966
|
+
req.scope
|
|
1967
|
+
);
|
|
1628
1968
|
const { accessToken, params, refreshToken } = refreshTokenResponse;
|
|
1629
|
-
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1969
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
1970
|
+
this._strategy,
|
|
1971
|
+
accessToken
|
|
1972
|
+
);
|
|
1630
1973
|
return {
|
|
1631
1974
|
response: await this.handleResult({
|
|
1632
1975
|
fullProfile,
|
|
@@ -1648,10 +1991,13 @@ class OAuth2AuthProvider {
|
|
|
1648
1991
|
profile
|
|
1649
1992
|
};
|
|
1650
1993
|
if (this.signInResolver) {
|
|
1651
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1994
|
+
response.backstageIdentity = await this.signInResolver(
|
|
1995
|
+
{
|
|
1996
|
+
result,
|
|
1997
|
+
profile
|
|
1998
|
+
},
|
|
1999
|
+
this.resolverContext
|
|
2000
|
+
);
|
|
1655
2001
|
}
|
|
1656
2002
|
return response;
|
|
1657
2003
|
}
|
|
@@ -1733,15 +2079,20 @@ class Oauth2ProxyAuthProvider {
|
|
|
1733
2079
|
}
|
|
1734
2080
|
async handleResult(result) {
|
|
1735
2081
|
const { profile } = await this.authHandler(result, this.resolverContext);
|
|
1736
|
-
const backstageSignInResult = await this.signInResolver(
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2082
|
+
const backstageSignInResult = await this.signInResolver(
|
|
2083
|
+
{
|
|
2084
|
+
result,
|
|
2085
|
+
profile
|
|
2086
|
+
},
|
|
2087
|
+
this.resolverContext
|
|
2088
|
+
);
|
|
1740
2089
|
return {
|
|
1741
2090
|
providerInfo: {
|
|
1742
2091
|
accessToken: result.accessToken
|
|
1743
2092
|
},
|
|
1744
|
-
backstageIdentity: prepareBackstageIdentityResponse(
|
|
2093
|
+
backstageIdentity: prepareBackstageIdentityResponse(
|
|
2094
|
+
backstageSignInResult
|
|
2095
|
+
),
|
|
1745
2096
|
profile
|
|
1746
2097
|
};
|
|
1747
2098
|
}
|
|
@@ -1820,17 +2171,26 @@ class OidcAuthProvider {
|
|
|
1820
2171
|
id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
|
|
1821
2172
|
scope: options.scope || ""
|
|
1822
2173
|
});
|
|
1823
|
-
const strategy = new openidClient.Strategy(
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2174
|
+
const strategy = new openidClient.Strategy(
|
|
2175
|
+
{
|
|
2176
|
+
client,
|
|
2177
|
+
passReqToCallback: false
|
|
2178
|
+
},
|
|
2179
|
+
(tokenset, userinfo, done) => {
|
|
2180
|
+
if (typeof done !== "function") {
|
|
2181
|
+
throw new Error(
|
|
2182
|
+
"OIDC IdP must provide a userinfo_endpoint in the metadata response"
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
done(
|
|
2186
|
+
void 0,
|
|
2187
|
+
{ tokenset, userinfo },
|
|
2188
|
+
{
|
|
2189
|
+
refreshToken: tokenset.refresh_token
|
|
2190
|
+
}
|
|
2191
|
+
);
|
|
1829
2192
|
}
|
|
1830
|
-
|
|
1831
|
-
refreshToken: tokenset.refresh_token
|
|
1832
|
-
});
|
|
1833
|
-
});
|
|
2193
|
+
);
|
|
1834
2194
|
strategy.error = console.error;
|
|
1835
2195
|
return { strategy, client };
|
|
1836
2196
|
}
|
|
@@ -1846,10 +2206,13 @@ class OidcAuthProvider {
|
|
|
1846
2206
|
profile
|
|
1847
2207
|
};
|
|
1848
2208
|
if (this.signInResolver) {
|
|
1849
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2209
|
+
response.backstageIdentity = await this.signInResolver(
|
|
2210
|
+
{
|
|
2211
|
+
result,
|
|
2212
|
+
profile
|
|
2213
|
+
},
|
|
2214
|
+
this.resolverContext
|
|
2215
|
+
);
|
|
1853
2216
|
}
|
|
1854
2217
|
return response;
|
|
1855
2218
|
}
|
|
@@ -1863,7 +2226,9 @@ const oidc = createAuthProviderIntegration({
|
|
|
1863
2226
|
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1864
2227
|
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1865
2228
|
const metadataUrl = envConfig.getString("metadataUrl");
|
|
1866
|
-
const tokenSignedResponseAlg = envConfig.getOptionalString(
|
|
2229
|
+
const tokenSignedResponseAlg = envConfig.getOptionalString(
|
|
2230
|
+
"tokenSignedResponseAlg"
|
|
2231
|
+
);
|
|
1867
2232
|
const scope = envConfig.getOptionalString("scope");
|
|
1868
2233
|
const prompt = envConfig.getOptionalString("prompt");
|
|
1869
2234
|
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ userinfo }) => ({
|
|
@@ -1906,24 +2271,33 @@ class OktaAuthProvider {
|
|
|
1906
2271
|
this.signInResolver = options.signInResolver;
|
|
1907
2272
|
this.authHandler = options.authHandler;
|
|
1908
2273
|
this.resolverContext = options.resolverContext;
|
|
1909
|
-
this.strategy = new passportOktaOauth.Strategy(
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2274
|
+
this.strategy = new passportOktaOauth.Strategy(
|
|
2275
|
+
{
|
|
2276
|
+
clientID: options.clientId,
|
|
2277
|
+
clientSecret: options.clientSecret,
|
|
2278
|
+
callbackURL: options.callbackUrl,
|
|
2279
|
+
audience: options.audience,
|
|
2280
|
+
authServerID: options.authServerId,
|
|
2281
|
+
idp: options.idp,
|
|
2282
|
+
passReqToCallback: false,
|
|
2283
|
+
store: this.store,
|
|
2284
|
+
response_type: "code"
|
|
2285
|
+
},
|
|
2286
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
2287
|
+
done(
|
|
2288
|
+
void 0,
|
|
2289
|
+
{
|
|
2290
|
+
accessToken,
|
|
2291
|
+
refreshToken,
|
|
2292
|
+
params,
|
|
2293
|
+
fullProfile
|
|
2294
|
+
},
|
|
2295
|
+
{
|
|
2296
|
+
refreshToken
|
|
2297
|
+
}
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
);
|
|
1927
2301
|
}
|
|
1928
2302
|
async start(req) {
|
|
1929
2303
|
return await executeRedirectStrategy(req, this.strategy, {
|
|
@@ -1941,8 +2315,15 @@ class OktaAuthProvider {
|
|
|
1941
2315
|
};
|
|
1942
2316
|
}
|
|
1943
2317
|
async refresh(req) {
|
|
1944
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1945
|
-
|
|
2318
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
2319
|
+
this.strategy,
|
|
2320
|
+
req.refreshToken,
|
|
2321
|
+
req.scope
|
|
2322
|
+
);
|
|
2323
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
2324
|
+
this.strategy,
|
|
2325
|
+
accessToken
|
|
2326
|
+
);
|
|
1946
2327
|
return {
|
|
1947
2328
|
response: await this.handleResult({
|
|
1948
2329
|
fullProfile,
|
|
@@ -1964,10 +2345,13 @@ class OktaAuthProvider {
|
|
|
1964
2345
|
profile
|
|
1965
2346
|
};
|
|
1966
2347
|
if (this.signInResolver) {
|
|
1967
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2348
|
+
response.backstageIdentity = await this.signInResolver(
|
|
2349
|
+
{
|
|
2350
|
+
result,
|
|
2351
|
+
profile
|
|
2352
|
+
},
|
|
2353
|
+
this.resolverContext
|
|
2354
|
+
);
|
|
1971
2355
|
}
|
|
1972
2356
|
return response;
|
|
1973
2357
|
}
|
|
@@ -1979,6 +2363,8 @@ const okta = createAuthProviderIntegration({
|
|
|
1979
2363
|
const clientId = envConfig.getString("clientId");
|
|
1980
2364
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1981
2365
|
const audience = envConfig.getString("audience");
|
|
2366
|
+
const authServerId = envConfig.getOptionalString("authServerId");
|
|
2367
|
+
const idp = envConfig.getOptionalString("idp");
|
|
1982
2368
|
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1983
2369
|
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1984
2370
|
if (!audience.startsWith("https://")) {
|
|
@@ -1989,6 +2375,8 @@ const okta = createAuthProviderIntegration({
|
|
|
1989
2375
|
});
|
|
1990
2376
|
const provider = new OktaAuthProvider({
|
|
1991
2377
|
audience,
|
|
2378
|
+
authServerId,
|
|
2379
|
+
idp,
|
|
1992
2380
|
clientId,
|
|
1993
2381
|
clientSecret,
|
|
1994
2382
|
callbackUrl,
|
|
@@ -2026,22 +2414,29 @@ class OneLoginProvider {
|
|
|
2026
2414
|
this.signInResolver = options.signInResolver;
|
|
2027
2415
|
this.authHandler = options.authHandler;
|
|
2028
2416
|
this.resolverContext = options.resolverContext;
|
|
2029
|
-
this._strategy = new passportOneloginOauth.Strategy(
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2417
|
+
this._strategy = new passportOneloginOauth.Strategy(
|
|
2418
|
+
{
|
|
2419
|
+
issuer: options.issuer,
|
|
2420
|
+
clientID: options.clientId,
|
|
2421
|
+
clientSecret: options.clientSecret,
|
|
2422
|
+
callbackURL: options.callbackUrl,
|
|
2423
|
+
passReqToCallback: false
|
|
2424
|
+
},
|
|
2425
|
+
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
2426
|
+
done(
|
|
2427
|
+
void 0,
|
|
2428
|
+
{
|
|
2429
|
+
accessToken,
|
|
2430
|
+
refreshToken,
|
|
2431
|
+
params,
|
|
2432
|
+
fullProfile
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
refreshToken
|
|
2436
|
+
}
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
);
|
|
2045
2440
|
}
|
|
2046
2441
|
async start(req) {
|
|
2047
2442
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -2059,8 +2454,15 @@ class OneLoginProvider {
|
|
|
2059
2454
|
};
|
|
2060
2455
|
}
|
|
2061
2456
|
async refresh(req) {
|
|
2062
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
2063
|
-
|
|
2457
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
2458
|
+
this._strategy,
|
|
2459
|
+
req.refreshToken,
|
|
2460
|
+
req.scope
|
|
2461
|
+
);
|
|
2462
|
+
const fullProfile = await executeFetchUserProfileStrategy(
|
|
2463
|
+
this._strategy,
|
|
2464
|
+
accessToken
|
|
2465
|
+
);
|
|
2064
2466
|
return {
|
|
2065
2467
|
response: await this.handleResult({
|
|
2066
2468
|
fullProfile,
|
|
@@ -2082,10 +2484,13 @@ class OneLoginProvider {
|
|
|
2082
2484
|
profile
|
|
2083
2485
|
};
|
|
2084
2486
|
if (this.signInResolver) {
|
|
2085
|
-
response.backstageIdentity = await this.signInResolver(
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2487
|
+
response.backstageIdentity = await this.signInResolver(
|
|
2488
|
+
{
|
|
2489
|
+
result,
|
|
2490
|
+
profile
|
|
2491
|
+
},
|
|
2492
|
+
this.resolverContext
|
|
2493
|
+
);
|
|
2089
2494
|
}
|
|
2090
2495
|
return response;
|
|
2091
2496
|
}
|
|
@@ -2135,17 +2540,23 @@ class SamlAuthProvider {
|
|
|
2135
2540
|
}
|
|
2136
2541
|
async frameHandler(req, res) {
|
|
2137
2542
|
try {
|
|
2138
|
-
const { result } = await executeFrameHandlerStrategy(
|
|
2543
|
+
const { result } = await executeFrameHandlerStrategy(
|
|
2544
|
+
req,
|
|
2545
|
+
this.strategy
|
|
2546
|
+
);
|
|
2139
2547
|
const { profile } = await this.authHandler(result, this.resolverContext);
|
|
2140
2548
|
const response = {
|
|
2141
2549
|
profile,
|
|
2142
2550
|
providerInfo: {}
|
|
2143
2551
|
};
|
|
2144
2552
|
if (this.signInResolver) {
|
|
2145
|
-
const signInResponse = await this.signInResolver(
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2553
|
+
const signInResponse = await this.signInResolver(
|
|
2554
|
+
{
|
|
2555
|
+
result,
|
|
2556
|
+
profile
|
|
2557
|
+
},
|
|
2558
|
+
this.resolverContext
|
|
2559
|
+
);
|
|
2149
2560
|
response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
|
|
2150
2561
|
}
|
|
2151
2562
|
return postMessageResponse(res, this.appUrl, {
|
|
@@ -2215,6 +2626,7 @@ const providers = Object.freeze({
|
|
|
2215
2626
|
auth0,
|
|
2216
2627
|
awsAlb,
|
|
2217
2628
|
bitbucket,
|
|
2629
|
+
cfAccess,
|
|
2218
2630
|
gcpIap,
|
|
2219
2631
|
github,
|
|
2220
2632
|
gitlab,
|
|
@@ -2296,7 +2708,9 @@ class TokenFactory {
|
|
|
2296
2708
|
try {
|
|
2297
2709
|
catalogModel.parseEntityRef(sub);
|
|
2298
2710
|
} catch (error) {
|
|
2299
|
-
throw new Error(
|
|
2711
|
+
throw new Error(
|
|
2712
|
+
'"sub" claim provided by the auth resolver is not a valid EntityRef.'
|
|
2713
|
+
);
|
|
2300
2714
|
}
|
|
2301
2715
|
this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
|
|
2302
2716
|
if (!key.alg) {
|
|
@@ -2360,12 +2774,17 @@ class TokenFactory {
|
|
|
2360
2774
|
}
|
|
2361
2775
|
}
|
|
2362
2776
|
|
|
2363
|
-
const migrationsDir = backendCommon.resolvePackagePath(
|
|
2777
|
+
const migrationsDir = backendCommon.resolvePackagePath(
|
|
2778
|
+
"@backstage/plugin-auth-backend",
|
|
2779
|
+
"migrations"
|
|
2780
|
+
);
|
|
2364
2781
|
const TABLE = "signing_keys";
|
|
2365
2782
|
const parseDate = (date) => {
|
|
2366
2783
|
const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
|
|
2367
2784
|
if (!parsedDate.isValid) {
|
|
2368
|
-
throw new Error(
|
|
2785
|
+
throw new Error(
|
|
2786
|
+
`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`
|
|
2787
|
+
);
|
|
2369
2788
|
}
|
|
2370
2789
|
return parsedDate.toJSDate();
|
|
2371
2790
|
};
|
|
@@ -2436,26 +2855,38 @@ class FirestoreKeyStore {
|
|
|
2436
2855
|
static async create(settings) {
|
|
2437
2856
|
const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
|
|
2438
2857
|
const database = new firestore.Firestore(firestoreSettings);
|
|
2439
|
-
return new FirestoreKeyStore(
|
|
2858
|
+
return new FirestoreKeyStore(
|
|
2859
|
+
database,
|
|
2860
|
+
path != null ? path : DEFAULT_DOCUMENT_PATH,
|
|
2861
|
+
timeout != null ? timeout : DEFAULT_TIMEOUT_MS
|
|
2862
|
+
);
|
|
2440
2863
|
}
|
|
2441
2864
|
static async verifyConnection(keyStore, logger) {
|
|
2442
2865
|
try {
|
|
2443
2866
|
await keyStore.verify();
|
|
2444
2867
|
} catch (error) {
|
|
2445
2868
|
if (process.env.NODE_ENV !== "development") {
|
|
2446
|
-
throw new Error(
|
|
2869
|
+
throw new Error(
|
|
2870
|
+
`Failed to connect to database: ${error.message}`
|
|
2871
|
+
);
|
|
2447
2872
|
}
|
|
2448
|
-
logger == null ? void 0 : logger.warn(
|
|
2873
|
+
logger == null ? void 0 : logger.warn(
|
|
2874
|
+
`Failed to connect to database: ${error.message}`
|
|
2875
|
+
);
|
|
2449
2876
|
}
|
|
2450
2877
|
}
|
|
2451
2878
|
async addKey(key) {
|
|
2452
|
-
await this.withTimeout(
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2879
|
+
await this.withTimeout(
|
|
2880
|
+
this.database.collection(this.path).doc(key.kid).set({
|
|
2881
|
+
kid: key.kid,
|
|
2882
|
+
key: JSON.stringify(key)
|
|
2883
|
+
})
|
|
2884
|
+
);
|
|
2456
2885
|
}
|
|
2457
2886
|
async listKeys() {
|
|
2458
|
-
const keys = await this.withTimeout(
|
|
2887
|
+
const keys = await this.withTimeout(
|
|
2888
|
+
this.database.collection(this.path).get()
|
|
2889
|
+
);
|
|
2459
2890
|
return {
|
|
2460
2891
|
items: keys.docs.map((key) => ({
|
|
2461
2892
|
key: key.data(),
|
|
@@ -2465,13 +2896,17 @@ class FirestoreKeyStore {
|
|
|
2465
2896
|
}
|
|
2466
2897
|
async removeKeys(kids) {
|
|
2467
2898
|
for (const kid of kids) {
|
|
2468
|
-
await this.withTimeout(
|
|
2899
|
+
await this.withTimeout(
|
|
2900
|
+
this.database.collection(this.path).doc(kid).delete()
|
|
2901
|
+
);
|
|
2469
2902
|
}
|
|
2470
2903
|
}
|
|
2471
2904
|
async withTimeout(operation) {
|
|
2472
|
-
const timer = new Promise(
|
|
2473
|
-
|
|
2474
|
-
|
|
2905
|
+
const timer = new Promise(
|
|
2906
|
+
(_, reject) => setTimeout(() => {
|
|
2907
|
+
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2908
|
+
}, this.timeout)
|
|
2909
|
+
);
|
|
2475
2910
|
return Promise.race([operation, timer]);
|
|
2476
2911
|
}
|
|
2477
2912
|
async verify() {
|
|
@@ -2499,15 +2934,20 @@ class KeyStores {
|
|
|
2499
2934
|
}
|
|
2500
2935
|
if (provider === "firestore") {
|
|
2501
2936
|
const settings = ks == null ? void 0 : ks.getConfig(provider);
|
|
2502
|
-
const keyStore = await FirestoreKeyStore.create(
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2937
|
+
const keyStore = await FirestoreKeyStore.create(
|
|
2938
|
+
lodash.pickBy(
|
|
2939
|
+
{
|
|
2940
|
+
projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
|
|
2941
|
+
keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
|
|
2942
|
+
host: settings == null ? void 0 : settings.getOptionalString("host"),
|
|
2943
|
+
port: settings == null ? void 0 : settings.getOptionalNumber("port"),
|
|
2944
|
+
ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
|
|
2945
|
+
path: settings == null ? void 0 : settings.getOptionalString("path"),
|
|
2946
|
+
timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
|
|
2947
|
+
},
|
|
2948
|
+
(value) => value !== void 0
|
|
2949
|
+
)
|
|
2950
|
+
);
|
|
2511
2951
|
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2512
2952
|
return keyStore;
|
|
2513
2953
|
}
|
|
@@ -2564,10 +3004,12 @@ class CatalogIdentityClient {
|
|
|
2564
3004
|
const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
|
|
2565
3005
|
logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
|
|
2566
3006
|
}
|
|
2567
|
-
const memberOf = entities.flatMap(
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
3007
|
+
const memberOf = entities.flatMap(
|
|
3008
|
+
(e) => {
|
|
3009
|
+
var _a, _b;
|
|
3010
|
+
return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.targetRef)) != null ? _b : [];
|
|
3011
|
+
}
|
|
3012
|
+
);
|
|
2571
3013
|
const newEntityRefs = [
|
|
2572
3014
|
...new Set(resolvedEntityRefs.map(catalogModel.stringifyEntityRef).concat(memberOf))
|
|
2573
3015
|
];
|
|
@@ -2578,7 +3020,9 @@ class CatalogIdentityClient {
|
|
|
2578
3020
|
|
|
2579
3021
|
function getDefaultOwnershipEntityRefs(entity) {
|
|
2580
3022
|
var _a, _b;
|
|
2581
|
-
const membershipRefs = (_b = (_a = entity.relations) == null ? void 0 : _a.filter(
|
|
3023
|
+
const membershipRefs = (_b = (_a = entity.relations) == null ? void 0 : _a.filter(
|
|
3024
|
+
(r) => r.type === catalogModel.RELATION_MEMBER_OF && r.targetRef.startsWith("group:")
|
|
3025
|
+
).map((r) => r.targetRef)) != null ? _b : [];
|
|
2582
3026
|
return Array.from(/* @__PURE__ */ new Set([catalogModel.stringifyEntityRef(entity), ...membershipRefs]));
|
|
2583
3027
|
}
|
|
2584
3028
|
class CatalogAuthResolverContext {
|
|
@@ -2594,7 +3038,13 @@ class CatalogAuthResolverContext {
|
|
|
2594
3038
|
catalogApi: options.catalogApi,
|
|
2595
3039
|
tokenManager: options.tokenManager
|
|
2596
3040
|
});
|
|
2597
|
-
return new CatalogAuthResolverContext(
|
|
3041
|
+
return new CatalogAuthResolverContext(
|
|
3042
|
+
options.logger,
|
|
3043
|
+
options.tokenIssuer,
|
|
3044
|
+
catalogIdentityClient,
|
|
3045
|
+
options.catalogApi,
|
|
3046
|
+
options.tokenManager
|
|
3047
|
+
);
|
|
2598
3048
|
}
|
|
2599
3049
|
async issueToken(params) {
|
|
2600
3050
|
const token = await this.tokenIssuer.issueToken(params);
|
|
@@ -2619,7 +3069,10 @@ class CatalogAuthResolverContext {
|
|
|
2619
3069
|
const res = await this.catalogApi.getEntities({ filter }, { token });
|
|
2620
3070
|
result = res.items;
|
|
2621
3071
|
} else if ("filter" in query) {
|
|
2622
|
-
const res = await this.catalogApi.getEntities(
|
|
3072
|
+
const res = await this.catalogApi.getEntities(
|
|
3073
|
+
{ filter: query.filter },
|
|
3074
|
+
{ token }
|
|
3075
|
+
);
|
|
2623
3076
|
result = res.items;
|
|
2624
3077
|
} else {
|
|
2625
3078
|
throw new errors.InputError("Invalid user lookup query");
|
|
@@ -2675,12 +3128,14 @@ async function createRouter(options) {
|
|
|
2675
3128
|
if (secret) {
|
|
2676
3129
|
router.use(cookieParser__default["default"](secret));
|
|
2677
3130
|
const enforceCookieSSL = authUrl.startsWith("https");
|
|
2678
|
-
router.use(
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3131
|
+
router.use(
|
|
3132
|
+
session__default["default"]({
|
|
3133
|
+
secret,
|
|
3134
|
+
saveUninitialized: false,
|
|
3135
|
+
resave: false,
|
|
3136
|
+
cookie: { secure: enforceCookieSSL ? "auto" : false }
|
|
3137
|
+
})
|
|
3138
|
+
);
|
|
2684
3139
|
router.use(passport__default["default"].initialize());
|
|
2685
3140
|
router.use(passport__default["default"].session());
|
|
2686
3141
|
} else {
|
|
@@ -2695,7 +3150,9 @@ async function createRouter(options) {
|
|
|
2695
3150
|
const providersConfig = config.getConfig("auth.providers");
|
|
2696
3151
|
const configuredProviders = providersConfig.keys();
|
|
2697
3152
|
const isOriginAllowed = createOriginFilter(config);
|
|
2698
|
-
for (const [providerId, providerFactory] of Object.entries(
|
|
3153
|
+
for (const [providerId, providerFactory] of Object.entries(
|
|
3154
|
+
allProviderFactories
|
|
3155
|
+
)) {
|
|
2699
3156
|
if (configuredProviders.includes(providerId)) {
|
|
2700
3157
|
logger.info(`Configuring provider, ${providerId}`);
|
|
2701
3158
|
try {
|
|
@@ -2724,28 +3181,37 @@ async function createRouter(options) {
|
|
|
2724
3181
|
}
|
|
2725
3182
|
if (provider.refresh) {
|
|
2726
3183
|
r.get("/refresh", provider.refresh.bind(provider));
|
|
3184
|
+
r.post("/refresh", provider.refresh.bind(provider));
|
|
2727
3185
|
}
|
|
2728
3186
|
router.use(`/${providerId}`, r);
|
|
2729
3187
|
} catch (e) {
|
|
2730
3188
|
errors.assertError(e);
|
|
2731
3189
|
if (process.env.NODE_ENV !== "development") {
|
|
2732
|
-
throw new Error(
|
|
3190
|
+
throw new Error(
|
|
3191
|
+
`Failed to initialize ${providerId} auth provider, ${e.message}`
|
|
3192
|
+
);
|
|
2733
3193
|
}
|
|
2734
3194
|
logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);
|
|
2735
3195
|
router.use(`/${providerId}`, () => {
|
|
2736
|
-
throw new errors.NotFoundError(
|
|
3196
|
+
throw new errors.NotFoundError(
|
|
3197
|
+
`Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under auth.providers.${providerId} are missing or the environment variables used are not defined. Check the auth backend plugin logs when the backend starts to see more details.`
|
|
3198
|
+
);
|
|
2737
3199
|
});
|
|
2738
3200
|
}
|
|
2739
3201
|
} else {
|
|
2740
3202
|
router.use(`/${providerId}`, () => {
|
|
2741
|
-
throw new errors.NotFoundError(
|
|
3203
|
+
throw new errors.NotFoundError(
|
|
3204
|
+
`No auth provider registered for '${providerId}'`
|
|
3205
|
+
);
|
|
2742
3206
|
});
|
|
2743
3207
|
}
|
|
2744
3208
|
}
|
|
2745
|
-
router.use(
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
3209
|
+
router.use(
|
|
3210
|
+
createOidcRouter({
|
|
3211
|
+
tokenIssuer,
|
|
3212
|
+
baseUrl: authUrl
|
|
3213
|
+
})
|
|
3214
|
+
);
|
|
2749
3215
|
router.use("/:provider/", (req) => {
|
|
2750
3216
|
const { provider } = req.params;
|
|
2751
3217
|
throw new errors.NotFoundError(`Unknown auth provider '${provider}'`);
|
|
@@ -2756,8 +3222,12 @@ function createOriginFilter(config) {
|
|
|
2756
3222
|
var _a;
|
|
2757
3223
|
const appUrl = config.getString("app.baseUrl");
|
|
2758
3224
|
const { origin: appOrigin } = new URL(appUrl);
|
|
2759
|
-
const allowedOrigins = config.getOptionalStringArray(
|
|
2760
|
-
|
|
3225
|
+
const allowedOrigins = config.getOptionalStringArray(
|
|
3226
|
+
"auth.experimentalExtraAllowedOrigins"
|
|
3227
|
+
);
|
|
3228
|
+
const allowedOriginPatterns = (_a = allowedOrigins == null ? void 0 : allowedOrigins.map(
|
|
3229
|
+
(pattern) => new minimatch.Minimatch(pattern, { nocase: true, noglobstar: true })
|
|
3230
|
+
)) != null ? _a : [];
|
|
2761
3231
|
return (origin) => {
|
|
2762
3232
|
if (origin === appOrigin) {
|
|
2763
3233
|
return true;
|