@backstage/plugin-auth-backend 0.15.0-next.3 → 0.15.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/config.d.ts +3 -0
- package/dist/index.cjs.js +843 -380
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +102 -1
- package/package.json +11 -11
package/dist/index.cjs.js
CHANGED
|
@@ -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,26 +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
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
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
|
+
);
|
|
1929
2301
|
}
|
|
1930
2302
|
async start(req) {
|
|
1931
2303
|
return await executeRedirectStrategy(req, this.strategy, {
|
|
@@ -1943,8 +2315,15 @@ class OktaAuthProvider {
|
|
|
1943
2315
|
};
|
|
1944
2316
|
}
|
|
1945
2317
|
async refresh(req) {
|
|
1946
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1947
|
-
|
|
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
|
+
);
|
|
1948
2327
|
return {
|
|
1949
2328
|
response: await this.handleResult({
|
|
1950
2329
|
fullProfile,
|
|
@@ -1966,10 +2345,13 @@ class OktaAuthProvider {
|
|
|
1966
2345
|
profile
|
|
1967
2346
|
};
|
|
1968
2347
|
if (this.signInResolver) {
|
|
1969
|
-
response.backstageIdentity = await this.signInResolver(
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2348
|
+
response.backstageIdentity = await this.signInResolver(
|
|
2349
|
+
{
|
|
2350
|
+
result,
|
|
2351
|
+
profile
|
|
2352
|
+
},
|
|
2353
|
+
this.resolverContext
|
|
2354
|
+
);
|
|
1973
2355
|
}
|
|
1974
2356
|
return response;
|
|
1975
2357
|
}
|
|
@@ -2032,22 +2414,29 @@ class OneLoginProvider {
|
|
|
2032
2414
|
this.signInResolver = options.signInResolver;
|
|
2033
2415
|
this.authHandler = options.authHandler;
|
|
2034
2416
|
this.resolverContext = options.resolverContext;
|
|
2035
|
-
this._strategy = new passportOneloginOauth.Strategy(
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
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
|
+
);
|
|
2051
2440
|
}
|
|
2052
2441
|
async start(req) {
|
|
2053
2442
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
@@ -2065,8 +2454,15 @@ class OneLoginProvider {
|
|
|
2065
2454
|
};
|
|
2066
2455
|
}
|
|
2067
2456
|
async refresh(req) {
|
|
2068
|
-
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
2069
|
-
|
|
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
|
+
);
|
|
2070
2466
|
return {
|
|
2071
2467
|
response: await this.handleResult({
|
|
2072
2468
|
fullProfile,
|
|
@@ -2088,10 +2484,13 @@ class OneLoginProvider {
|
|
|
2088
2484
|
profile
|
|
2089
2485
|
};
|
|
2090
2486
|
if (this.signInResolver) {
|
|
2091
|
-
response.backstageIdentity = await this.signInResolver(
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2487
|
+
response.backstageIdentity = await this.signInResolver(
|
|
2488
|
+
{
|
|
2489
|
+
result,
|
|
2490
|
+
profile
|
|
2491
|
+
},
|
|
2492
|
+
this.resolverContext
|
|
2493
|
+
);
|
|
2095
2494
|
}
|
|
2096
2495
|
return response;
|
|
2097
2496
|
}
|
|
@@ -2141,17 +2540,23 @@ class SamlAuthProvider {
|
|
|
2141
2540
|
}
|
|
2142
2541
|
async frameHandler(req, res) {
|
|
2143
2542
|
try {
|
|
2144
|
-
const { result } = await executeFrameHandlerStrategy(
|
|
2543
|
+
const { result } = await executeFrameHandlerStrategy(
|
|
2544
|
+
req,
|
|
2545
|
+
this.strategy
|
|
2546
|
+
);
|
|
2145
2547
|
const { profile } = await this.authHandler(result, this.resolverContext);
|
|
2146
2548
|
const response = {
|
|
2147
2549
|
profile,
|
|
2148
2550
|
providerInfo: {}
|
|
2149
2551
|
};
|
|
2150
2552
|
if (this.signInResolver) {
|
|
2151
|
-
const signInResponse = await this.signInResolver(
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2553
|
+
const signInResponse = await this.signInResolver(
|
|
2554
|
+
{
|
|
2555
|
+
result,
|
|
2556
|
+
profile
|
|
2557
|
+
},
|
|
2558
|
+
this.resolverContext
|
|
2559
|
+
);
|
|
2155
2560
|
response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
|
|
2156
2561
|
}
|
|
2157
2562
|
return postMessageResponse(res, this.appUrl, {
|
|
@@ -2221,6 +2626,7 @@ const providers = Object.freeze({
|
|
|
2221
2626
|
auth0,
|
|
2222
2627
|
awsAlb,
|
|
2223
2628
|
bitbucket,
|
|
2629
|
+
cfAccess,
|
|
2224
2630
|
gcpIap,
|
|
2225
2631
|
github,
|
|
2226
2632
|
gitlab,
|
|
@@ -2302,7 +2708,9 @@ class TokenFactory {
|
|
|
2302
2708
|
try {
|
|
2303
2709
|
catalogModel.parseEntityRef(sub);
|
|
2304
2710
|
} catch (error) {
|
|
2305
|
-
throw new Error(
|
|
2711
|
+
throw new Error(
|
|
2712
|
+
'"sub" claim provided by the auth resolver is not a valid EntityRef.'
|
|
2713
|
+
);
|
|
2306
2714
|
}
|
|
2307
2715
|
this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
|
|
2308
2716
|
if (!key.alg) {
|
|
@@ -2366,12 +2774,17 @@ class TokenFactory {
|
|
|
2366
2774
|
}
|
|
2367
2775
|
}
|
|
2368
2776
|
|
|
2369
|
-
const migrationsDir = backendCommon.resolvePackagePath(
|
|
2777
|
+
const migrationsDir = backendCommon.resolvePackagePath(
|
|
2778
|
+
"@backstage/plugin-auth-backend",
|
|
2779
|
+
"migrations"
|
|
2780
|
+
);
|
|
2370
2781
|
const TABLE = "signing_keys";
|
|
2371
2782
|
const parseDate = (date) => {
|
|
2372
2783
|
const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
|
|
2373
2784
|
if (!parsedDate.isValid) {
|
|
2374
|
-
throw new Error(
|
|
2785
|
+
throw new Error(
|
|
2786
|
+
`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`
|
|
2787
|
+
);
|
|
2375
2788
|
}
|
|
2376
2789
|
return parsedDate.toJSDate();
|
|
2377
2790
|
};
|
|
@@ -2442,26 +2855,38 @@ class FirestoreKeyStore {
|
|
|
2442
2855
|
static async create(settings) {
|
|
2443
2856
|
const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
|
|
2444
2857
|
const database = new firestore.Firestore(firestoreSettings);
|
|
2445
|
-
return new FirestoreKeyStore(
|
|
2858
|
+
return new FirestoreKeyStore(
|
|
2859
|
+
database,
|
|
2860
|
+
path != null ? path : DEFAULT_DOCUMENT_PATH,
|
|
2861
|
+
timeout != null ? timeout : DEFAULT_TIMEOUT_MS
|
|
2862
|
+
);
|
|
2446
2863
|
}
|
|
2447
2864
|
static async verifyConnection(keyStore, logger) {
|
|
2448
2865
|
try {
|
|
2449
2866
|
await keyStore.verify();
|
|
2450
2867
|
} catch (error) {
|
|
2451
2868
|
if (process.env.NODE_ENV !== "development") {
|
|
2452
|
-
throw new Error(
|
|
2869
|
+
throw new Error(
|
|
2870
|
+
`Failed to connect to database: ${error.message}`
|
|
2871
|
+
);
|
|
2453
2872
|
}
|
|
2454
|
-
logger == null ? void 0 : logger.warn(
|
|
2873
|
+
logger == null ? void 0 : logger.warn(
|
|
2874
|
+
`Failed to connect to database: ${error.message}`
|
|
2875
|
+
);
|
|
2455
2876
|
}
|
|
2456
2877
|
}
|
|
2457
2878
|
async addKey(key) {
|
|
2458
|
-
await this.withTimeout(
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
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
|
+
);
|
|
2462
2885
|
}
|
|
2463
2886
|
async listKeys() {
|
|
2464
|
-
const keys = await this.withTimeout(
|
|
2887
|
+
const keys = await this.withTimeout(
|
|
2888
|
+
this.database.collection(this.path).get()
|
|
2889
|
+
);
|
|
2465
2890
|
return {
|
|
2466
2891
|
items: keys.docs.map((key) => ({
|
|
2467
2892
|
key: key.data(),
|
|
@@ -2471,13 +2896,17 @@ class FirestoreKeyStore {
|
|
|
2471
2896
|
}
|
|
2472
2897
|
async removeKeys(kids) {
|
|
2473
2898
|
for (const kid of kids) {
|
|
2474
|
-
await this.withTimeout(
|
|
2899
|
+
await this.withTimeout(
|
|
2900
|
+
this.database.collection(this.path).doc(kid).delete()
|
|
2901
|
+
);
|
|
2475
2902
|
}
|
|
2476
2903
|
}
|
|
2477
2904
|
async withTimeout(operation) {
|
|
2478
|
-
const timer = new Promise(
|
|
2479
|
-
|
|
2480
|
-
|
|
2905
|
+
const timer = new Promise(
|
|
2906
|
+
(_, reject) => setTimeout(() => {
|
|
2907
|
+
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2908
|
+
}, this.timeout)
|
|
2909
|
+
);
|
|
2481
2910
|
return Promise.race([operation, timer]);
|
|
2482
2911
|
}
|
|
2483
2912
|
async verify() {
|
|
@@ -2505,15 +2934,20 @@ class KeyStores {
|
|
|
2505
2934
|
}
|
|
2506
2935
|
if (provider === "firestore") {
|
|
2507
2936
|
const settings = ks == null ? void 0 : ks.getConfig(provider);
|
|
2508
|
-
const keyStore = await FirestoreKeyStore.create(
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
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
|
+
);
|
|
2517
2951
|
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2518
2952
|
return keyStore;
|
|
2519
2953
|
}
|
|
@@ -2570,10 +3004,12 @@ class CatalogIdentityClient {
|
|
|
2570
3004
|
const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
|
|
2571
3005
|
logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
|
|
2572
3006
|
}
|
|
2573
|
-
const memberOf = entities.flatMap(
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
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
|
+
);
|
|
2577
3013
|
const newEntityRefs = [
|
|
2578
3014
|
...new Set(resolvedEntityRefs.map(catalogModel.stringifyEntityRef).concat(memberOf))
|
|
2579
3015
|
];
|
|
@@ -2584,7 +3020,9 @@ class CatalogIdentityClient {
|
|
|
2584
3020
|
|
|
2585
3021
|
function getDefaultOwnershipEntityRefs(entity) {
|
|
2586
3022
|
var _a, _b;
|
|
2587
|
-
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 : [];
|
|
2588
3026
|
return Array.from(/* @__PURE__ */ new Set([catalogModel.stringifyEntityRef(entity), ...membershipRefs]));
|
|
2589
3027
|
}
|
|
2590
3028
|
class CatalogAuthResolverContext {
|
|
@@ -2600,7 +3038,13 @@ class CatalogAuthResolverContext {
|
|
|
2600
3038
|
catalogApi: options.catalogApi,
|
|
2601
3039
|
tokenManager: options.tokenManager
|
|
2602
3040
|
});
|
|
2603
|
-
return new CatalogAuthResolverContext(
|
|
3041
|
+
return new CatalogAuthResolverContext(
|
|
3042
|
+
options.logger,
|
|
3043
|
+
options.tokenIssuer,
|
|
3044
|
+
catalogIdentityClient,
|
|
3045
|
+
options.catalogApi,
|
|
3046
|
+
options.tokenManager
|
|
3047
|
+
);
|
|
2604
3048
|
}
|
|
2605
3049
|
async issueToken(params) {
|
|
2606
3050
|
const token = await this.tokenIssuer.issueToken(params);
|
|
@@ -2625,7 +3069,10 @@ class CatalogAuthResolverContext {
|
|
|
2625
3069
|
const res = await this.catalogApi.getEntities({ filter }, { token });
|
|
2626
3070
|
result = res.items;
|
|
2627
3071
|
} else if ("filter" in query) {
|
|
2628
|
-
const res = await this.catalogApi.getEntities(
|
|
3072
|
+
const res = await this.catalogApi.getEntities(
|
|
3073
|
+
{ filter: query.filter },
|
|
3074
|
+
{ token }
|
|
3075
|
+
);
|
|
2629
3076
|
result = res.items;
|
|
2630
3077
|
} else {
|
|
2631
3078
|
throw new errors.InputError("Invalid user lookup query");
|
|
@@ -2681,12 +3128,14 @@ async function createRouter(options) {
|
|
|
2681
3128
|
if (secret) {
|
|
2682
3129
|
router.use(cookieParser__default["default"](secret));
|
|
2683
3130
|
const enforceCookieSSL = authUrl.startsWith("https");
|
|
2684
|
-
router.use(
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
3131
|
+
router.use(
|
|
3132
|
+
session__default["default"]({
|
|
3133
|
+
secret,
|
|
3134
|
+
saveUninitialized: false,
|
|
3135
|
+
resave: false,
|
|
3136
|
+
cookie: { secure: enforceCookieSSL ? "auto" : false }
|
|
3137
|
+
})
|
|
3138
|
+
);
|
|
2690
3139
|
router.use(passport__default["default"].initialize());
|
|
2691
3140
|
router.use(passport__default["default"].session());
|
|
2692
3141
|
} else {
|
|
@@ -2701,7 +3150,9 @@ async function createRouter(options) {
|
|
|
2701
3150
|
const providersConfig = config.getConfig("auth.providers");
|
|
2702
3151
|
const configuredProviders = providersConfig.keys();
|
|
2703
3152
|
const isOriginAllowed = createOriginFilter(config);
|
|
2704
|
-
for (const [providerId, providerFactory] of Object.entries(
|
|
3153
|
+
for (const [providerId, providerFactory] of Object.entries(
|
|
3154
|
+
allProviderFactories
|
|
3155
|
+
)) {
|
|
2705
3156
|
if (configuredProviders.includes(providerId)) {
|
|
2706
3157
|
logger.info(`Configuring provider, ${providerId}`);
|
|
2707
3158
|
try {
|
|
@@ -2736,23 +3187,31 @@ async function createRouter(options) {
|
|
|
2736
3187
|
} catch (e) {
|
|
2737
3188
|
errors.assertError(e);
|
|
2738
3189
|
if (process.env.NODE_ENV !== "development") {
|
|
2739
|
-
throw new Error(
|
|
3190
|
+
throw new Error(
|
|
3191
|
+
`Failed to initialize ${providerId} auth provider, ${e.message}`
|
|
3192
|
+
);
|
|
2740
3193
|
}
|
|
2741
3194
|
logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);
|
|
2742
3195
|
router.use(`/${providerId}`, () => {
|
|
2743
|
-
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
|
+
);
|
|
2744
3199
|
});
|
|
2745
3200
|
}
|
|
2746
3201
|
} else {
|
|
2747
3202
|
router.use(`/${providerId}`, () => {
|
|
2748
|
-
throw new errors.NotFoundError(
|
|
3203
|
+
throw new errors.NotFoundError(
|
|
3204
|
+
`No auth provider registered for '${providerId}'`
|
|
3205
|
+
);
|
|
2749
3206
|
});
|
|
2750
3207
|
}
|
|
2751
3208
|
}
|
|
2752
|
-
router.use(
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
3209
|
+
router.use(
|
|
3210
|
+
createOidcRouter({
|
|
3211
|
+
tokenIssuer,
|
|
3212
|
+
baseUrl: authUrl
|
|
3213
|
+
})
|
|
3214
|
+
);
|
|
2756
3215
|
router.use("/:provider/", (req) => {
|
|
2757
3216
|
const { provider } = req.params;
|
|
2758
3217
|
throw new errors.NotFoundError(`Unknown auth provider '${provider}'`);
|
|
@@ -2763,8 +3222,12 @@ function createOriginFilter(config) {
|
|
|
2763
3222
|
var _a;
|
|
2764
3223
|
const appUrl = config.getString("app.baseUrl");
|
|
2765
3224
|
const { origin: appOrigin } = new URL(appUrl);
|
|
2766
|
-
const allowedOrigins = config.getOptionalStringArray(
|
|
2767
|
-
|
|
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 : [];
|
|
2768
3231
|
return (origin) => {
|
|
2769
3232
|
if (origin === appOrigin) {
|
|
2770
3233
|
return true;
|