@backstage/plugin-auth-backend 0.5.2 → 0.7.0-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/dist/index.cjs.js CHANGED
@@ -5,32 +5,32 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var express = require('express');
6
6
  var Router = require('express-promise-router');
7
7
  var cookieParser = require('cookie-parser');
8
- var passportGithub2 = require('passport-github2');
9
- var jwtDecoder = require('jwt-decode');
8
+ var OAuth2Strategy = require('passport-oauth2');
10
9
  var errors = require('@backstage/errors');
11
10
  var pickBy = require('lodash/pickBy');
12
11
  var crypto = require('crypto');
13
12
  var url = require('url');
14
13
  var catalogModel = require('@backstage/catalog-model');
15
- var passportGitlab2 = require('passport-gitlab2');
16
- var passportGoogleOauth20 = require('passport-google-oauth20');
17
- var passportMicrosoft = require('passport-microsoft');
18
- var got = require('got');
19
- var OAuth2Strategy = require('passport-oauth2');
20
- var openidClient = require('openid-client');
21
- var passportOktaOauth = require('passport-okta-oauth');
22
- var passportBitbucketOauth2 = require('passport-bitbucket-oauth2');
14
+ var jwtDecoder = require('jwt-decode');
23
15
  var fetch = require('node-fetch');
24
16
  var NodeCache = require('node-cache');
25
17
  var jose = require('jose');
26
- var passportSaml = require('passport-saml');
27
- var passportOneloginOauth = require('passport-onelogin-oauth');
28
- var catalogClient = require('@backstage/catalog-client');
18
+ var passportBitbucketOauth2 = require('passport-bitbucket-oauth2');
19
+ var passportGithub2 = require('passport-github2');
20
+ var passportGitlab2 = require('passport-gitlab2');
21
+ var passportGoogleOauth20 = require('passport-google-oauth20');
22
+ var passportMicrosoft = require('passport-microsoft');
29
23
  var uuid = require('uuid');
30
24
  var luxon = require('luxon');
31
25
  var backendCommon = require('@backstage/backend-common');
32
26
  var firestore = require('@google-cloud/firestore');
33
27
  var lodash = require('lodash');
28
+ var openidClient = require('openid-client');
29
+ var passportOktaOauth = require('passport-okta-oauth');
30
+ var passportOneloginOauth = require('passport-onelogin-oauth');
31
+ var passportSaml = require('passport-saml');
32
+ var googleAuthLibrary = require('google-auth-library');
33
+ var catalogClient = require('@backstage/catalog-client');
34
34
  var session = require('express-session');
35
35
  var passport = require('passport');
36
36
  var minimatch = require('minimatch');
@@ -58,129 +58,69 @@ function _interopNamespace(e) {
58
58
  var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
59
59
  var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
60
60
  var cookieParser__default = /*#__PURE__*/_interopDefaultLegacy(cookieParser);
61
- var jwtDecoder__default = /*#__PURE__*/_interopDefaultLegacy(jwtDecoder);
61
+ var OAuth2Strategy__default = /*#__PURE__*/_interopDefaultLegacy(OAuth2Strategy);
62
62
  var pickBy__default = /*#__PURE__*/_interopDefaultLegacy(pickBy);
63
63
  var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
64
64
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
65
- var got__default = /*#__PURE__*/_interopDefaultLegacy(got);
66
- var OAuth2Strategy__default = /*#__PURE__*/_interopDefaultLegacy(OAuth2Strategy);
65
+ var jwtDecoder__default = /*#__PURE__*/_interopDefaultLegacy(jwtDecoder);
67
66
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
68
67
  var NodeCache__default = /*#__PURE__*/_interopDefaultLegacy(NodeCache);
69
68
  var session__default = /*#__PURE__*/_interopDefaultLegacy(session);
70
69
  var passport__default = /*#__PURE__*/_interopDefaultLegacy(passport);
71
70
 
72
- const makeProfileInfo = (profile, idToken) => {
73
- var _a, _b;
74
- let email = void 0;
75
- if (profile.emails && profile.emails.length > 0) {
76
- const [firstEmail] = profile.emails;
77
- email = firstEmail.value;
78
- }
79
- let picture = void 0;
80
- if (profile.avatarUrl) {
81
- picture = profile.avatarUrl;
82
- } else if (profile.photos && profile.photos.length > 0) {
83
- const [firstPhoto] = profile.photos;
84
- picture = firstPhoto.value;
85
- }
86
- let displayName = (_b = (_a = profile.displayName) != null ? _a : profile.username) != null ? _b : profile.id;
87
- if ((!email || !picture || !displayName) && idToken) {
88
- try {
89
- const decoded = jwtDecoder__default["default"](idToken);
90
- if (!email && decoded.email) {
91
- email = decoded.email;
92
- }
93
- if (!picture && decoded.picture) {
94
- picture = decoded.picture;
95
- }
96
- if (!displayName && decoded.name) {
97
- displayName = decoded.name;
98
- }
99
- } catch (e) {
100
- throw new Error(`Failed to parse id token and get profile info, ${e}`);
71
+ const defaultScopes = ["offline_access", "read:me"];
72
+ class AtlassianStrategy extends OAuth2Strategy__default["default"] {
73
+ constructor(options, verify) {
74
+ if (!options.scope) {
75
+ throw new TypeError("Atlassian requires a scope option");
101
76
  }
102
- }
103
- return {
104
- email,
105
- picture,
106
- displayName
107
- };
108
- };
109
- const executeRedirectStrategy = async (req, providerStrategy, options) => {
110
- return new Promise((resolve) => {
111
- const strategy = Object.create(providerStrategy);
112
- strategy.redirect = (url, status) => {
113
- resolve({ url, status: status != null ? status : void 0 });
114
- };
115
- strategy.authenticate(req, { ...options });
116
- });
117
- };
118
- const executeFrameHandlerStrategy = async (req, providerStrategy) => {
119
- return new Promise((resolve, reject) => {
120
- const strategy = Object.create(providerStrategy);
121
- strategy.success = (result, privateInfo) => {
122
- resolve({ result, privateInfo });
123
- };
124
- strategy.fail = (info) => {
125
- var _a;
126
- reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
127
- };
128
- strategy.error = (error) => {
129
- var _a;
130
- let message = `Authentication failed, ${error.message}`;
131
- if ((_a = error.oauthError) == null ? void 0 : _a.data) {
132
- try {
133
- const errorData = JSON.parse(error.oauthError.data);
134
- if (errorData.message) {
135
- message += ` - ${errorData.message}`;
136
- }
137
- } catch (parseError) {
138
- message += ` - ${error.oauthError}`;
139
- }
140
- }
141
- reject(new Error(message));
77
+ const scopes = options.scope.split(" ");
78
+ const optionsWithURLs = {
79
+ ...options,
80
+ authorizationURL: `https://auth.atlassian.com/authorize`,
81
+ tokenURL: `https://auth.atlassian.com/oauth/token`,
82
+ scope: Array.from(/* @__PURE__ */ new Set([...defaultScopes, ...scopes]))
142
83
  };
143
- strategy.redirect = () => {
144
- reject(new Error("Unexpected redirect"));
84
+ super(optionsWithURLs, verify);
85
+ this.profileURL = "https://api.atlassian.com/me";
86
+ this.name = "atlassian";
87
+ this._oauth2.useAuthorizationHeaderforGET(true);
88
+ }
89
+ authorizationParams() {
90
+ return {
91
+ audience: "api.atlassian.com",
92
+ prompt: "consent"
145
93
  };
146
- strategy.authenticate(req, {});
147
- });
148
- };
149
- const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
150
- return new Promise((resolve, reject) => {
151
- const anyStrategy = providerStrategy;
152
- const OAuth2 = anyStrategy._oauth2.constructor;
153
- const oauth2 = new OAuth2(anyStrategy._oauth2._clientId, anyStrategy._oauth2._clientSecret, anyStrategy._oauth2._baseSite, anyStrategy._oauth2._authorizeUrl, anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, anyStrategy._oauth2._customHeaders);
154
- oauth2.getOAuthAccessToken(refreshToken, {
155
- scope,
156
- grant_type: "refresh_token"
157
- }, (err, accessToken, newRefreshToken, params) => {
94
+ }
95
+ userProfile(accessToken, done) {
96
+ this._oauth2.get(this.profileURL, accessToken, (err, body) => {
158
97
  if (err) {
159
- reject(new Error(`Failed to refresh access token ${err.toString()}`));
98
+ return done(new OAuth2Strategy.InternalOAuthError("Failed to fetch user profile", err.statusCode));
160
99
  }
161
- if (!accessToken) {
162
- reject(new Error(`Failed to refresh access token, no access token received`));
100
+ if (!body) {
101
+ return done(new Error("Failed to fetch user profile, body cannot be empty"));
163
102
  }
164
- resolve({
165
- accessToken,
166
- refreshToken: newRefreshToken,
167
- params
168
- });
169
- });
170
- });
171
- };
172
- const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
173
- return new Promise((resolve, reject) => {
174
- const anyStrategy = providerStrategy;
175
- anyStrategy.userProfile(accessToken, (error, rawProfile) => {
176
- if (error) {
177
- reject(error);
178
- } else {
179
- resolve(rawProfile);
103
+ try {
104
+ const json = typeof body !== "string" ? body.toString() : body;
105
+ const profile = AtlassianStrategy.parse(json);
106
+ return done(null, profile);
107
+ } catch (e) {
108
+ return done(new Error("Failed to parse user profile"));
180
109
  }
181
110
  });
182
- });
183
- };
111
+ }
112
+ static parse(json) {
113
+ const resp = JSON.parse(json);
114
+ return {
115
+ id: resp.account_id,
116
+ provider: "atlassian",
117
+ username: resp.nickname,
118
+ displayName: resp.name,
119
+ emails: [{ value: resp.email }],
120
+ photos: [{ value: resp.picture }]
121
+ };
122
+ }
123
+ }
184
124
 
185
125
  const readState = (stateString) => {
186
126
  var _a, _b;
@@ -462,10 +402,10 @@ class OAuthAdapter {
462
402
  }
463
403
  const scope = (_b = (_a = req.query.scope) == null ? void 0 : _a.toString()) != null ? _b : "";
464
404
  const forwardReq = Object.assign(req, { scope, refreshToken });
465
- const response = await this.handlers.refresh(forwardReq);
405
+ const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
466
406
  const backstageIdentity = await this.populateIdentity(response.backstageIdentity);
467
- if (response.providerInfo.refreshToken && response.providerInfo.refreshToken !== refreshToken) {
468
- this.setRefreshTokenCookie(res, response.providerInfo.refreshToken);
407
+ if (newRefreshToken && newRefreshToken !== refreshToken) {
408
+ this.setRefreshTokenCookie(res, newRefreshToken);
469
409
  }
470
410
  res.status(200).json({ ...response, backstageIdentity });
471
411
  } catch (error) {
@@ -490,63 +430,176 @@ class OAuthAdapter {
490
430
  }
491
431
  }
492
432
 
493
- class CatalogIdentityClient {
494
- constructor(options) {
495
- this.catalogApi = options.catalogApi;
496
- this.tokenIssuer = options.tokenIssuer;
433
+ const makeProfileInfo = (profile, idToken) => {
434
+ var _a, _b;
435
+ let email = void 0;
436
+ if (profile.emails && profile.emails.length > 0) {
437
+ const [firstEmail] = profile.emails;
438
+ email = firstEmail.value;
497
439
  }
498
- async findUser(query) {
499
- const filter = {
500
- kind: "user"
501
- };
502
- for (const [key, value] of Object.entries(query.annotations)) {
503
- filter[`metadata.annotations.${key}`] = value;
504
- }
505
- const token = await this.tokenIssuer.issueToken({
506
- claims: { sub: "backstage.io/auth-backend" }
507
- });
508
- const { items } = await this.catalogApi.getEntities({ filter }, { token });
509
- if (items.length !== 1) {
510
- if (items.length > 1) {
511
- throw new errors.ConflictError("User lookup resulted in multiple matches");
512
- } else {
513
- throw new errors.NotFoundError("User not found");
514
- }
515
- }
516
- return items[0];
440
+ let picture = void 0;
441
+ if (profile.avatarUrl) {
442
+ picture = profile.avatarUrl;
443
+ } else if (profile.photos && profile.photos.length > 0) {
444
+ const [firstPhoto] = profile.photos;
445
+ picture = firstPhoto.value;
517
446
  }
518
- async resolveCatalogMembership(query) {
519
- const { entityRefs, logger } = query;
520
- const resolvedEntityRefs = entityRefs.map((ref) => {
521
- try {
522
- const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
523
- defaultKind: "user",
524
- defaultNamespace: "default"
525
- });
526
- return parsedRef;
527
- } catch {
528
- logger == null ? void 0 : logger.warn(`Failed to parse entityRef from ${ref}, ignoring`);
529
- return null;
447
+ let displayName = (_b = (_a = profile.displayName) != null ? _a : profile.username) != null ? _b : profile.id;
448
+ if ((!email || !picture || !displayName) && idToken) {
449
+ try {
450
+ const decoded = jwtDecoder__default["default"](idToken);
451
+ if (!email && decoded.email) {
452
+ email = decoded.email;
530
453
  }
531
- }).filter((ref) => ref !== null);
532
- const filter = resolvedEntityRefs.map((ref) => ({
533
- kind: ref.kind,
534
- "metadata.namespace": ref.namespace,
535
- "metadata.name": ref.name
536
- }));
537
- const entities = await this.catalogApi.getEntities({ filter }).then((r) => r.items);
538
- if (entityRefs.length !== entities.length) {
539
- const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
540
- const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
541
- logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
454
+ if (!picture && decoded.picture) {
455
+ picture = decoded.picture;
456
+ }
457
+ if (!displayName && decoded.name) {
458
+ displayName = decoded.name;
459
+ }
460
+ } catch (e) {
461
+ throw new Error(`Failed to parse id token and get profile info, ${e}`);
542
462
  }
543
- const memberOf = entities.flatMap((e) => {
544
- var _a, _b;
545
- return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.target)) != null ? _b : [];
546
- });
547
- const newEntityRefs = [
548
- ...new Set(resolvedEntityRefs.concat(memberOf).map(catalogModel.stringifyEntityRef))
549
- ];
463
+ }
464
+ return {
465
+ email,
466
+ picture,
467
+ displayName
468
+ };
469
+ };
470
+ const executeRedirectStrategy = async (req, providerStrategy, options) => {
471
+ return new Promise((resolve) => {
472
+ const strategy = Object.create(providerStrategy);
473
+ strategy.redirect = (url, status) => {
474
+ resolve({ url, status: status != null ? status : void 0 });
475
+ };
476
+ strategy.authenticate(req, { ...options });
477
+ });
478
+ };
479
+ const executeFrameHandlerStrategy = async (req, providerStrategy) => {
480
+ return new Promise((resolve, reject) => {
481
+ const strategy = Object.create(providerStrategy);
482
+ strategy.success = (result, privateInfo) => {
483
+ resolve({ result, privateInfo });
484
+ };
485
+ strategy.fail = (info) => {
486
+ var _a;
487
+ reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
488
+ };
489
+ strategy.error = (error) => {
490
+ var _a;
491
+ let message = `Authentication failed, ${error.message}`;
492
+ if ((_a = error.oauthError) == null ? void 0 : _a.data) {
493
+ try {
494
+ const errorData = JSON.parse(error.oauthError.data);
495
+ if (errorData.message) {
496
+ message += ` - ${errorData.message}`;
497
+ }
498
+ } catch (parseError) {
499
+ message += ` - ${error.oauthError}`;
500
+ }
501
+ }
502
+ reject(new Error(message));
503
+ };
504
+ strategy.redirect = () => {
505
+ reject(new Error("Unexpected redirect"));
506
+ };
507
+ strategy.authenticate(req, {});
508
+ });
509
+ };
510
+ const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
511
+ return new Promise((resolve, reject) => {
512
+ const anyStrategy = providerStrategy;
513
+ const OAuth2 = anyStrategy._oauth2.constructor;
514
+ const oauth2 = new OAuth2(anyStrategy._oauth2._clientId, anyStrategy._oauth2._clientSecret, anyStrategy._oauth2._baseSite, anyStrategy._oauth2._authorizeUrl, anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, anyStrategy._oauth2._customHeaders);
515
+ oauth2.getOAuthAccessToken(refreshToken, {
516
+ scope,
517
+ grant_type: "refresh_token"
518
+ }, (err, accessToken, newRefreshToken, params) => {
519
+ if (err) {
520
+ reject(new Error(`Failed to refresh access token ${err.toString()}`));
521
+ }
522
+ if (!accessToken) {
523
+ reject(new Error(`Failed to refresh access token, no access token received`));
524
+ }
525
+ resolve({
526
+ accessToken,
527
+ refreshToken: newRefreshToken,
528
+ params
529
+ });
530
+ });
531
+ });
532
+ };
533
+ const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
534
+ return new Promise((resolve, reject) => {
535
+ const anyStrategy = providerStrategy;
536
+ anyStrategy.userProfile(accessToken, (error, rawProfile) => {
537
+ if (error) {
538
+ reject(error);
539
+ } else {
540
+ resolve(rawProfile);
541
+ }
542
+ });
543
+ });
544
+ };
545
+
546
+ class CatalogIdentityClient {
547
+ constructor(options) {
548
+ this.catalogApi = options.catalogApi;
549
+ this.tokenIssuer = options.tokenIssuer;
550
+ }
551
+ async findUser(query) {
552
+ const filter = {
553
+ kind: "user"
554
+ };
555
+ for (const [key, value] of Object.entries(query.annotations)) {
556
+ filter[`metadata.annotations.${key}`] = value;
557
+ }
558
+ const token = await this.tokenIssuer.issueToken({
559
+ claims: { sub: "backstage.io/auth-backend" }
560
+ });
561
+ const { items } = await this.catalogApi.getEntities({ filter }, { token });
562
+ if (items.length !== 1) {
563
+ if (items.length > 1) {
564
+ throw new errors.ConflictError("User lookup resulted in multiple matches");
565
+ } else {
566
+ throw new errors.NotFoundError("User not found");
567
+ }
568
+ }
569
+ return items[0];
570
+ }
571
+ async resolveCatalogMembership(query) {
572
+ const { entityRefs, logger } = query;
573
+ const resolvedEntityRefs = entityRefs.map((ref) => {
574
+ try {
575
+ const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
576
+ defaultKind: "user",
577
+ defaultNamespace: "default"
578
+ });
579
+ return parsedRef;
580
+ } catch {
581
+ logger == null ? void 0 : logger.warn(`Failed to parse entityRef from ${ref}, ignoring`);
582
+ return null;
583
+ }
584
+ }).filter((ref) => ref !== null);
585
+ const filter = resolvedEntityRefs.map((ref) => ({
586
+ kind: ref.kind,
587
+ "metadata.namespace": ref.namespace,
588
+ "metadata.name": ref.name
589
+ }));
590
+ const entities = await this.catalogApi.getEntities({ filter }).then((r) => r.items);
591
+ if (entityRefs.length !== entities.length) {
592
+ const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
593
+ const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
594
+ logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
595
+ }
596
+ const memberOf = entities.flatMap((e) => {
597
+ var _a, _b;
598
+ return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.target)) != null ? _b : [];
599
+ });
600
+ const newEntityRefs = [
601
+ ...new Set(resolvedEntityRefs.concat(memberOf).map(catalogModel.stringifyEntityRef))
602
+ ];
550
603
  logger == null ? void 0 : logger.debug(`Found catalog membership: ${newEntityRefs.join()}`);
551
604
  return newEntityRefs;
552
605
  }
@@ -562,61 +615,58 @@ function getEntityClaims(entity) {
562
615
  };
563
616
  }
564
617
 
565
- class GithubAuthProvider {
618
+ const atlassianDefaultAuthHandler = async ({
619
+ fullProfile,
620
+ params
621
+ }) => ({
622
+ profile: makeProfileInfo(fullProfile, params.id_token)
623
+ });
624
+ class AtlassianAuthProvider {
566
625
  constructor(options) {
567
- this.signInResolver = options.signInResolver;
568
- this.authHandler = options.authHandler;
569
- this.stateEncoder = options.stateEncoder;
570
- this.tokenIssuer = options.tokenIssuer;
571
626
  this.catalogIdentityClient = options.catalogIdentityClient;
572
627
  this.logger = options.logger;
573
- this._strategy = new passportGithub2.Strategy({
628
+ this.tokenIssuer = options.tokenIssuer;
629
+ this.authHandler = options.authHandler;
630
+ this.signInResolver = options.signInResolver;
631
+ this._strategy = new AtlassianStrategy({
574
632
  clientID: options.clientId,
575
633
  clientSecret: options.clientSecret,
576
634
  callbackURL: options.callbackUrl,
577
- tokenURL: options.tokenUrl,
578
- userProfileURL: options.userProfileUrl,
579
- authorizationURL: options.authorizationUrl
635
+ scope: options.scopes
580
636
  }, (accessToken, refreshToken, params, fullProfile, done) => {
581
- done(void 0, { fullProfile, params, accessToken }, { refreshToken });
637
+ done(void 0, {
638
+ fullProfile,
639
+ accessToken,
640
+ refreshToken,
641
+ params
642
+ });
582
643
  });
583
644
  }
584
645
  async start(req) {
585
646
  return await executeRedirectStrategy(req, this._strategy, {
586
- scope: req.scope,
587
- state: (await this.stateEncoder(req)).encodedState
647
+ state: encodeState(req.state)
588
648
  });
589
649
  }
590
650
  async handler(req) {
591
- const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
651
+ const { result } = await executeFrameHandlerStrategy(req, this._strategy);
592
652
  return {
593
653
  response: await this.handleResult(result),
594
- refreshToken: privateInfo.refreshToken
654
+ refreshToken: result.refreshToken
595
655
  };
596
656
  }
597
- async refresh(req) {
598
- const {
599
- accessToken,
600
- refreshToken: newRefreshToken,
601
- params
602
- } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
603
- const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
604
- return this.handleResult({
605
- fullProfile,
606
- params,
607
- accessToken,
608
- refreshToken: newRefreshToken
609
- });
610
- }
611
657
  async handleResult(result) {
612
- const { profile } = await this.authHandler(result);
613
- const expiresInStr = result.params.expires_in;
658
+ const context = {
659
+ logger: this.logger,
660
+ catalogIdentityClient: this.catalogIdentityClient,
661
+ tokenIssuer: this.tokenIssuer
662
+ };
663
+ const { profile } = await this.authHandler(result, context);
614
664
  const response = {
615
665
  providerInfo: {
666
+ idToken: result.params.id_token,
616
667
  accessToken: result.accessToken,
617
- refreshToken: result.refreshToken,
618
668
  scope: result.params.scope,
619
- expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
669
+ expiresInSeconds: result.params.expires_in
620
670
  },
621
671
  profile
622
672
  };
@@ -624,24 +674,24 @@ class GithubAuthProvider {
624
674
  response.backstageIdentity = await this.signInResolver({
625
675
  result,
626
676
  profile
627
- }, {
628
- tokenIssuer: this.tokenIssuer,
629
- catalogIdentityClient: this.catalogIdentityClient,
630
- logger: this.logger
631
- });
677
+ }, context);
632
678
  }
633
679
  return response;
634
680
  }
681
+ async refresh(req) {
682
+ const { accessToken, params, refreshToken } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
683
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
684
+ return {
685
+ response: await this.handleResult({
686
+ fullProfile,
687
+ params,
688
+ accessToken
689
+ }),
690
+ refreshToken
691
+ };
692
+ }
635
693
  }
636
- const githubDefaultSignInResolver = async (info, ctx) => {
637
- const { fullProfile } = info.result;
638
- const userId = fullProfile.username || fullProfile.id;
639
- const token = await ctx.tokenIssuer.issueToken({
640
- claims: { sub: userId, ent: [`user:default/${userId}`] }
641
- });
642
- return { id: userId, token };
643
- };
644
- const createGithubProvider = (options) => {
694
+ const createAtlassianProvider = (options) => {
645
695
  return ({
646
696
  providerId,
647
697
  globalConfig,
@@ -650,90 +700,76 @@ const createGithubProvider = (options) => {
650
700
  catalogApi,
651
701
  logger
652
702
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
653
- var _a, _b, _c;
703
+ var _a, _b;
654
704
  const clientId = envConfig.getString("clientId");
655
705
  const clientSecret = envConfig.getString("clientSecret");
656
- const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
657
- const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
658
- const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` : void 0;
659
- const tokenUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/access_token` : void 0;
660
- const userProfileUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/api/v3/user` : void 0;
661
- const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
706
+ const scopes = envConfig.getString("scopes");
707
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
662
708
  const catalogIdentityClient = new CatalogIdentityClient({
663
709
  catalogApi,
664
710
  tokenIssuer
665
711
  });
666
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
667
- profile: makeProfileInfo(fullProfile)
668
- });
669
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : githubDefaultSignInResolver;
670
- const signInResolver = (info) => signInResolverFn(info, {
671
- catalogIdentityClient,
672
- tokenIssuer,
673
- logger
674
- });
675
- const stateEncoder = (_c = options == null ? void 0 : options.stateEncoder) != null ? _c : async (req) => {
676
- return { encodedState: encodeState(req.state) };
677
- };
678
- const provider = new GithubAuthProvider({
712
+ const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : atlassianDefaultAuthHandler;
713
+ const provider = new AtlassianAuthProvider({
679
714
  clientId,
680
715
  clientSecret,
716
+ scopes,
681
717
  callbackUrl,
682
- tokenUrl,
683
- userProfileUrl,
684
- authorizationUrl,
685
- signInResolver,
686
718
  authHandler,
687
- tokenIssuer,
719
+ signInResolver: (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver,
688
720
  catalogIdentityClient,
689
- stateEncoder,
690
- logger
721
+ logger,
722
+ tokenIssuer
691
723
  });
692
724
  return OAuthAdapter.fromConfig(globalConfig, provider, {
693
- persistScopes: true,
725
+ disableRefresh: true,
694
726
  providerId,
695
727
  tokenIssuer
696
728
  });
697
729
  });
698
730
  };
699
731
 
700
- const gitlabDefaultSignInResolver = async (info, ctx) => {
701
- const { profile, result } = info;
702
- let id = result.fullProfile.id;
703
- if (profile.email) {
704
- id = profile.email.split("@")[0];
705
- }
706
- const token = await ctx.tokenIssuer.issueToken({
707
- claims: { sub: id, ent: [`user:default/${id}`] }
708
- });
709
- return { id, token };
710
- };
711
- const gitlabDefaultAuthHandler = async ({
712
- fullProfile,
713
- params
714
- }) => ({
715
- profile: makeProfileInfo(fullProfile, params.id_token)
716
- });
717
- class GitlabAuthProvider {
732
+ class Auth0Strategy extends OAuth2Strategy__default["default"] {
733
+ constructor(options, verify) {
734
+ const optionsWithURLs = {
735
+ ...options,
736
+ authorizationURL: `https://${options.domain}/authorize`,
737
+ tokenURL: `https://${options.domain}/oauth/token`,
738
+ userInfoURL: `https://${options.domain}/userinfo`,
739
+ apiUrl: `https://${options.domain}/api`
740
+ };
741
+ super(optionsWithURLs, verify);
742
+ }
743
+ }
744
+
745
+ class Auth0AuthProvider {
718
746
  constructor(options) {
747
+ this.signInResolver = options.signInResolver;
748
+ this.authHandler = options.authHandler;
749
+ this.tokenIssuer = options.tokenIssuer;
719
750
  this.catalogIdentityClient = options.catalogIdentityClient;
720
751
  this.logger = options.logger;
721
- this.tokenIssuer = options.tokenIssuer;
722
- this.authHandler = options.authHandler;
723
- this.signInResolver = options.signInResolver;
724
- this._strategy = new passportGitlab2.Strategy({
752
+ this._strategy = new Auth0Strategy({
725
753
  clientID: options.clientId,
726
754
  clientSecret: options.clientSecret,
727
755
  callbackURL: options.callbackUrl,
728
- baseURL: options.baseUrl
756
+ domain: options.domain,
757
+ passReqToCallback: false
729
758
  }, (accessToken, refreshToken, params, fullProfile, done) => {
730
- done(void 0, { fullProfile, params, accessToken }, {
759
+ done(void 0, {
760
+ fullProfile,
761
+ accessToken,
762
+ refreshToken,
763
+ params
764
+ }, {
731
765
  refreshToken
732
766
  });
733
767
  });
734
768
  }
735
769
  async start(req) {
736
770
  return await executeRedirectStrategy(req, this._strategy, {
771
+ accessType: "offline",
772
+ prompt: "consent",
737
773
  scope: req.scope,
738
774
  state: encodeState(req.state)
739
775
  });
@@ -746,26 +782,28 @@ class GitlabAuthProvider {
746
782
  };
747
783
  }
748
784
  async refresh(req) {
749
- const {
750
- accessToken,
751
- refreshToken: newRefreshToken,
752
- params
753
- } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
785
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
754
786
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
755
- return this.handleResult({
756
- fullProfile,
757
- params,
758
- accessToken,
759
- refreshToken: newRefreshToken
760
- });
787
+ return {
788
+ response: await this.handleResult({
789
+ fullProfile,
790
+ params,
791
+ accessToken
792
+ }),
793
+ refreshToken
794
+ };
761
795
  }
762
796
  async handleResult(result) {
763
- const { profile } = await this.authHandler(result);
797
+ const context = {
798
+ logger: this.logger,
799
+ catalogIdentityClient: this.catalogIdentityClient,
800
+ tokenIssuer: this.tokenIssuer
801
+ };
802
+ const { profile } = await this.authHandler(result, context);
764
803
  const response = {
765
804
  providerInfo: {
766
805
  idToken: result.params.id_token,
767
806
  accessToken: result.accessToken,
768
- refreshToken: result.refreshToken,
769
807
  scope: result.params.scope,
770
808
  expiresInSeconds: result.params.expires_in
771
809
  },
@@ -775,16 +813,20 @@ class GitlabAuthProvider {
775
813
  response.backstageIdentity = await this.signInResolver({
776
814
  result,
777
815
  profile
778
- }, {
779
- tokenIssuer: this.tokenIssuer,
780
- catalogIdentityClient: this.catalogIdentityClient,
781
- logger: this.logger
782
- });
816
+ }, context);
783
817
  }
784
818
  return response;
785
819
  }
786
820
  }
787
- const createGitlabProvider = (options) => {
821
+ const defaultSignInResolver$1 = async (info) => {
822
+ const { profile } = info;
823
+ if (!profile.email) {
824
+ throw new Error("Profile does not contain an email");
825
+ }
826
+ const id = profile.email.split("@")[0];
827
+ return { id, token: "" };
828
+ };
829
+ const createAuth0Provider = (options) => {
788
830
  return ({
789
831
  providerId,
790
832
  globalConfig,
@@ -793,50 +835,175 @@ const createGitlabProvider = (options) => {
793
835
  catalogApi,
794
836
  logger
795
837
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
796
- var _a, _b, _c;
838
+ var _a, _b;
797
839
  const clientId = envConfig.getString("clientId");
798
840
  const clientSecret = envConfig.getString("clientSecret");
799
- const audience = envConfig.getOptionalString("audience");
800
- const baseUrl = audience || "https://gitlab.com";
841
+ const domain = envConfig.getString("domain");
801
842
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
802
843
  const catalogIdentityClient = new CatalogIdentityClient({
803
844
  catalogApi,
804
845
  tokenIssuer
805
846
  });
806
- const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : gitlabDefaultAuthHandler;
807
- const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : gitlabDefaultSignInResolver;
808
- const signInResolver = (info) => signInResolverFn(info, {
809
- catalogIdentityClient,
810
- tokenIssuer,
811
- logger
847
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
848
+ profile: makeProfileInfo(fullProfile, params.id_token)
812
849
  });
813
- const provider = new GitlabAuthProvider({
850
+ const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver$1;
851
+ const provider = new Auth0AuthProvider({
814
852
  clientId,
815
853
  clientSecret,
816
854
  callbackUrl,
817
- baseUrl,
855
+ domain,
818
856
  authHandler,
819
857
  signInResolver,
858
+ tokenIssuer,
820
859
  catalogIdentityClient,
821
- logger,
822
- tokenIssuer
860
+ logger
823
861
  });
824
862
  return OAuthAdapter.fromConfig(globalConfig, provider, {
825
- disableRefresh: false,
863
+ disableRefresh: true,
826
864
  providerId,
827
865
  tokenIssuer
828
866
  });
829
867
  });
830
868
  };
831
869
 
832
- class GoogleAuthProvider {
870
+ const ALB_JWT_HEADER = "x-amzn-oidc-data";
871
+ const ALB_ACCESS_TOKEN_HEADER = "x-amzn-oidc-accesstoken";
872
+ const getJWTHeaders = (input) => {
873
+ const encoded = input.split(".")[0];
874
+ return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
875
+ };
876
+ class AwsAlbAuthProvider {
877
+ constructor(options) {
878
+ this.region = options.region;
879
+ this.issuer = options.issuer;
880
+ this.authHandler = options.authHandler;
881
+ this.signInResolver = options.signInResolver;
882
+ this.tokenIssuer = options.tokenIssuer;
883
+ this.catalogIdentityClient = options.catalogIdentityClient;
884
+ this.logger = options.logger;
885
+ this.keyCache = new NodeCache__default["default"]({ stdTTL: 3600 });
886
+ }
887
+ frameHandler() {
888
+ return Promise.resolve(void 0);
889
+ }
890
+ async refresh(req, res) {
891
+ try {
892
+ const result = await this.getResult(req);
893
+ const response = await this.handleResult(result);
894
+ res.json(response);
895
+ } catch (e) {
896
+ this.logger.error("Exception occurred during AWS ALB token refresh", e);
897
+ res.status(401);
898
+ res.end();
899
+ }
900
+ }
901
+ start() {
902
+ return Promise.resolve(void 0);
903
+ }
904
+ async getResult(req) {
905
+ const jwt = req.header(ALB_JWT_HEADER);
906
+ const accessToken = req.header(ALB_ACCESS_TOKEN_HEADER);
907
+ if (jwt === void 0) {
908
+ throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_JWT_HEADER}`);
909
+ }
910
+ if (accessToken === void 0) {
911
+ throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_ACCESS_TOKEN_HEADER}`);
912
+ }
913
+ try {
914
+ const headers = getJWTHeaders(jwt);
915
+ const key = await this.getKey(headers.kid);
916
+ const claims = jose.JWT.verify(jwt, key);
917
+ if (this.issuer && claims.iss !== this.issuer) {
918
+ throw new errors.AuthenticationError("Issuer mismatch on JWT token");
919
+ }
920
+ const fullProfile = {
921
+ provider: "unknown",
922
+ id: claims.sub,
923
+ displayName: claims.name,
924
+ username: claims.email.split("@")[0].toLowerCase(),
925
+ name: {
926
+ familyName: claims.family_name,
927
+ givenName: claims.given_name
928
+ },
929
+ emails: [{ value: claims.email.toLowerCase() }],
930
+ photos: [{ value: claims.picture }]
931
+ };
932
+ return {
933
+ fullProfile,
934
+ expiresInSeconds: claims.exp,
935
+ accessToken
936
+ };
937
+ } catch (e) {
938
+ throw new Error(`Exception occurred during JWT processing: ${e}`);
939
+ }
940
+ }
941
+ async handleResult(result) {
942
+ const context = {
943
+ tokenIssuer: this.tokenIssuer,
944
+ catalogIdentityClient: this.catalogIdentityClient,
945
+ logger: this.logger
946
+ };
947
+ const { profile } = await this.authHandler(result, context);
948
+ const backstageIdentity = await this.signInResolver({
949
+ result,
950
+ profile
951
+ }, context);
952
+ return {
953
+ providerInfo: {
954
+ accessToken: result.accessToken,
955
+ expiresInSeconds: result.expiresInSeconds
956
+ },
957
+ backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
958
+ profile
959
+ };
960
+ }
961
+ async getKey(keyId) {
962
+ const optionalCacheKey = this.keyCache.get(keyId);
963
+ if (optionalCacheKey) {
964
+ return crypto__namespace.createPublicKey(optionalCacheKey);
965
+ }
966
+ const keyText = await fetch__default["default"](`https://public-keys.auth.elb.${this.region}.amazonaws.com/${keyId}`).then((response) => response.text());
967
+ const keyValue = crypto__namespace.createPublicKey(keyText);
968
+ this.keyCache.set(keyId, keyValue.export({ format: "pem", type: "spki" }));
969
+ return keyValue;
970
+ }
971
+ }
972
+ const createAwsAlbProvider = (options) => {
973
+ return ({ config, tokenIssuer, catalogApi, logger }) => {
974
+ const region = config.getString("region");
975
+ const issuer = config.getOptionalString("iss");
976
+ if ((options == null ? void 0 : options.signIn.resolver) === void 0) {
977
+ throw new Error("SignInResolver is required to use this authentication provider");
978
+ }
979
+ const catalogIdentityClient = new CatalogIdentityClient({
980
+ catalogApi,
981
+ tokenIssuer
982
+ });
983
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
984
+ profile: makeProfileInfo(fullProfile)
985
+ });
986
+ const signInResolver = options == null ? void 0 : options.signIn.resolver;
987
+ return new AwsAlbAuthProvider({
988
+ region,
989
+ issuer,
990
+ signInResolver,
991
+ authHandler,
992
+ tokenIssuer,
993
+ catalogIdentityClient,
994
+ logger
995
+ });
996
+ };
997
+ };
998
+
999
+ class BitbucketAuthProvider {
833
1000
  constructor(options) {
834
1001
  this.signInResolver = options.signInResolver;
835
1002
  this.authHandler = options.authHandler;
836
1003
  this.tokenIssuer = options.tokenIssuer;
837
1004
  this.catalogIdentityClient = options.catalogIdentityClient;
838
1005
  this.logger = options.logger;
839
- this._strategy = new passportGoogleOauth20.Strategy({
1006
+ this._strategy = new passportBitbucketOauth2.Strategy({
840
1007
  clientID: options.clientId,
841
1008
  clientSecret: options.clientSecret,
842
1009
  callbackURL: options.callbackUrl,
@@ -868,17 +1035,25 @@ class GoogleAuthProvider {
868
1035
  };
869
1036
  }
870
1037
  async refresh(req) {
871
- const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1038
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
872
1039
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
873
- return this.handleResult({
874
- fullProfile,
875
- params,
876
- accessToken,
877
- refreshToken: req.refreshToken
878
- });
1040
+ return {
1041
+ response: await this.handleResult({
1042
+ fullProfile,
1043
+ params,
1044
+ accessToken
1045
+ }),
1046
+ refreshToken
1047
+ };
879
1048
  }
880
1049
  async handleResult(result) {
881
- const { profile } = await this.authHandler(result);
1050
+ result.fullProfile.avatarUrl = result.fullProfile._json.links.avatar.href;
1051
+ const context = {
1052
+ logger: this.logger,
1053
+ catalogIdentityClient: this.catalogIdentityClient,
1054
+ tokenIssuer: this.tokenIssuer
1055
+ };
1056
+ const { profile } = await this.authHandler(result, context);
882
1057
  const response = {
883
1058
  providerInfo: {
884
1059
  idToken: result.params.id_token,
@@ -892,52 +1067,40 @@ class GoogleAuthProvider {
892
1067
  response.backstageIdentity = await this.signInResolver({
893
1068
  result,
894
1069
  profile
895
- }, {
896
- tokenIssuer: this.tokenIssuer,
897
- catalogIdentityClient: this.catalogIdentityClient,
898
- logger: this.logger
899
- });
1070
+ }, context);
900
1071
  }
901
1072
  return response;
902
1073
  }
903
1074
  }
904
- const googleEmailSignInResolver = async (info, ctx) => {
905
- const { profile } = info;
906
- if (!profile.email) {
907
- throw new Error("Google profile contained no email");
1075
+ const bitbucketUsernameSignInResolver = async (info, ctx) => {
1076
+ const { result } = info;
1077
+ if (!result.fullProfile.username) {
1078
+ throw new Error("Bitbucket profile contained no Username");
908
1079
  }
909
1080
  const entity = await ctx.catalogIdentityClient.findUser({
910
1081
  annotations: {
911
- "google.com/email": profile.email
1082
+ "bitbucket.org/username": result.fullProfile.username
912
1083
  }
913
1084
  });
914
1085
  const claims = getEntityClaims(entity);
915
1086
  const token = await ctx.tokenIssuer.issueToken({ claims });
916
1087
  return { id: entity.metadata.name, entity, token };
917
1088
  };
918
- const googleDefaultSignInResolver = async (info, ctx) => {
919
- const { profile } = info;
920
- if (!profile.email) {
921
- throw new Error("Google profile contained no email");
922
- }
923
- let userId;
924
- try {
925
- const entity = await ctx.catalogIdentityClient.findUser({
926
- annotations: {
927
- "google.com/email": profile.email
928
- }
929
- });
930
- userId = entity.metadata.name;
931
- } catch (error) {
932
- ctx.logger.warn(`Failed to look up user, ${error}, falling back to allowing login based on email pattern, this will probably break in the future`);
933
- userId = profile.email.split("@")[0];
1089
+ const bitbucketUserIdSignInResolver = async (info, ctx) => {
1090
+ const { result } = info;
1091
+ if (!result.fullProfile.id) {
1092
+ throw new Error("Bitbucket profile contained no User ID");
934
1093
  }
935
- const token = await ctx.tokenIssuer.issueToken({
936
- claims: { sub: userId, ent: [`user:default/${userId}`] }
1094
+ const entity = await ctx.catalogIdentityClient.findUser({
1095
+ annotations: {
1096
+ "bitbucket.org/user-id": result.fullProfile.id
1097
+ }
937
1098
  });
938
- return { id: userId, token };
1099
+ const claims = getEntityClaims(entity);
1100
+ const token = await ctx.tokenIssuer.issueToken({ claims });
1101
+ return { id: entity.metadata.name, entity, token };
939
1102
  };
940
- const createGoogleProvider = (options) => {
1103
+ const createBitbucketProvider = (options) => {
941
1104
  return ({
942
1105
  providerId,
943
1106
  globalConfig,
@@ -946,7 +1109,7 @@ const createGoogleProvider = (options) => {
946
1109
  catalogApi,
947
1110
  logger
948
1111
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
949
- var _a, _b;
1112
+ var _a;
950
1113
  const clientId = envConfig.getString("clientId");
951
1114
  const clientSecret = envConfig.getString("clientSecret");
952
1115
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
@@ -957,17 +1120,11 @@ const createGoogleProvider = (options) => {
957
1120
  const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
958
1121
  profile: makeProfileInfo(fullProfile, params.id_token)
959
1122
  });
960
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : googleDefaultSignInResolver;
961
- const signInResolver = (info) => signInResolverFn(info, {
962
- catalogIdentityClient,
963
- tokenIssuer,
964
- logger
965
- });
966
- const provider = new GoogleAuthProvider({
1123
+ const provider = new BitbucketAuthProvider({
967
1124
  clientId,
968
1125
  clientSecret,
969
1126
  callbackUrl,
970
- signInResolver,
1127
+ signInResolver: (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver,
971
1128
  authHandler,
972
1129
  tokenIssuer,
973
1130
  catalogIdentityClient,
@@ -981,28 +1138,29 @@ const createGoogleProvider = (options) => {
981
1138
  });
982
1139
  };
983
1140
 
984
- class MicrosoftAuthProvider {
1141
+ class GithubAuthProvider {
985
1142
  constructor(options) {
986
1143
  this.signInResolver = options.signInResolver;
987
1144
  this.authHandler = options.authHandler;
1145
+ this.stateEncoder = options.stateEncoder;
988
1146
  this.tokenIssuer = options.tokenIssuer;
989
- this.logger = options.logger;
990
1147
  this.catalogIdentityClient = options.catalogIdentityClient;
991
- this._strategy = new passportMicrosoft.Strategy({
1148
+ this.logger = options.logger;
1149
+ this._strategy = new passportGithub2.Strategy({
992
1150
  clientID: options.clientId,
993
1151
  clientSecret: options.clientSecret,
994
1152
  callbackURL: options.callbackUrl,
995
- authorizationURL: options.authorizationUrl,
996
1153
  tokenURL: options.tokenUrl,
997
- passReqToCallback: false
1154
+ userProfileURL: options.userProfileUrl,
1155
+ authorizationURL: options.authorizationUrl
998
1156
  }, (accessToken, refreshToken, params, fullProfile, done) => {
999
- done(void 0, { fullProfile, accessToken, params }, { refreshToken });
1157
+ done(void 0, { fullProfile, params, accessToken }, { refreshToken });
1000
1158
  });
1001
1159
  }
1002
1160
  async start(req) {
1003
1161
  return await executeRedirectStrategy(req, this._strategy, {
1004
1162
  scope: req.scope,
1005
- state: encodeState(req.state)
1163
+ state: (await this.stateEncoder(req)).encodedState
1006
1164
  });
1007
1165
  }
1008
1166
  async handler(req) {
@@ -1013,25 +1171,30 @@ class MicrosoftAuthProvider {
1013
1171
  };
1014
1172
  }
1015
1173
  async refresh(req) {
1016
- const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1174
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1017
1175
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1018
- return this.handleResult({
1019
- fullProfile,
1020
- params,
1021
- accessToken,
1022
- refreshToken: req.refreshToken
1023
- });
1176
+ return {
1177
+ response: await this.handleResult({
1178
+ fullProfile,
1179
+ params,
1180
+ accessToken
1181
+ }),
1182
+ refreshToken
1183
+ };
1024
1184
  }
1025
1185
  async handleResult(result) {
1026
- const photo = await this.getUserPhoto(result.accessToken);
1027
- result.fullProfile.photos = photo ? [{ value: photo }] : void 0;
1028
- const { profile } = await this.authHandler(result);
1186
+ const context = {
1187
+ logger: this.logger,
1188
+ catalogIdentityClient: this.catalogIdentityClient,
1189
+ tokenIssuer: this.tokenIssuer
1190
+ };
1191
+ const { profile } = await this.authHandler(result, context);
1192
+ const expiresInStr = result.params.expires_in;
1029
1193
  const response = {
1030
1194
  providerInfo: {
1031
- idToken: result.params.id_token,
1032
1195
  accessToken: result.accessToken,
1033
1196
  scope: result.params.scope,
1034
- expiresInSeconds: result.params.expires_in
1197
+ expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
1035
1198
  },
1036
1199
  profile
1037
1200
  };
@@ -1039,58 +1202,20 @@ class MicrosoftAuthProvider {
1039
1202
  response.backstageIdentity = await this.signInResolver({
1040
1203
  result,
1041
1204
  profile
1042
- }, {
1043
- tokenIssuer: this.tokenIssuer,
1044
- catalogIdentityClient: this.catalogIdentityClient,
1045
- logger: this.logger
1046
- });
1205
+ }, context);
1047
1206
  }
1048
1207
  return response;
1049
1208
  }
1050
- getUserPhoto(accessToken) {
1051
- return new Promise((resolve) => {
1052
- got__default["default"].get("https://graph.microsoft.com/v1.0/me/photos/48x48/$value", {
1053
- encoding: "binary",
1054
- responseType: "buffer",
1055
- headers: {
1056
- Authorization: `Bearer ${accessToken}`
1057
- }
1058
- }).then((photoData) => {
1059
- const photoURL = `data:image/jpeg;base64,${Buffer.from(photoData.body).toString("base64")}`;
1060
- resolve(photoURL);
1061
- }).catch((error) => {
1062
- this.logger.warn(`Could not retrieve user profile photo from Microsoft Graph API: ${error}`);
1063
- resolve(void 0);
1064
- });
1065
- });
1066
- }
1067
1209
  }
1068
- const microsoftEmailSignInResolver = async (info, ctx) => {
1069
- const { profile } = info;
1070
- if (!profile.email) {
1071
- throw new Error("Microsoft profile contained no email");
1072
- }
1073
- const entity = await ctx.catalogIdentityClient.findUser({
1074
- annotations: {
1075
- "microsoft.com/email": profile.email
1076
- }
1077
- });
1078
- const claims = getEntityClaims(entity);
1079
- const token = await ctx.tokenIssuer.issueToken({ claims });
1080
- return { id: entity.metadata.name, entity, token };
1081
- };
1082
- const microsoftDefaultSignInResolver = async (info, ctx) => {
1083
- const { profile } = info;
1084
- if (!profile.email) {
1085
- throw new Error("Profile contained no email");
1086
- }
1087
- const userId = profile.email.split("@")[0];
1210
+ const githubDefaultSignInResolver = async (info, ctx) => {
1211
+ const { fullProfile } = info.result;
1212
+ const userId = fullProfile.username || fullProfile.id;
1088
1213
  const token = await ctx.tokenIssuer.issueToken({
1089
1214
  claims: { sub: userId, ent: [`user:default/${userId}`] }
1090
1215
  });
1091
1216
  return { id: userId, token };
1092
1217
  };
1093
- const createMicrosoftProvider = (options) => {
1218
+ const createGithubProvider = (options) => {
1094
1219
  return ({
1095
1220
  providerId,
1096
1221
  globalConfig,
@@ -1099,79 +1224,90 @@ const createMicrosoftProvider = (options) => {
1099
1224
  catalogApi,
1100
1225
  logger
1101
1226
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1102
- var _a, _b;
1227
+ var _a, _b, _c;
1103
1228
  const clientId = envConfig.getString("clientId");
1104
1229
  const clientSecret = envConfig.getString("clientSecret");
1105
- const tenantId = envConfig.getString("tenantId");
1106
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1107
- const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
1108
- const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
1230
+ const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
1231
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
1232
+ const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` : void 0;
1233
+ const tokenUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/access_token` : void 0;
1234
+ const userProfileUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/api/v3/user` : void 0;
1235
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1109
1236
  const catalogIdentityClient = new CatalogIdentityClient({
1110
1237
  catalogApi,
1111
1238
  tokenIssuer
1112
1239
  });
1113
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
1114
- profile: makeProfileInfo(fullProfile, params.id_token)
1240
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
1241
+ profile: makeProfileInfo(fullProfile)
1115
1242
  });
1116
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : microsoftDefaultSignInResolver;
1243
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : githubDefaultSignInResolver;
1117
1244
  const signInResolver = (info) => signInResolverFn(info, {
1118
1245
  catalogIdentityClient,
1119
1246
  tokenIssuer,
1120
1247
  logger
1121
1248
  });
1122
- const provider = new MicrosoftAuthProvider({
1249
+ const stateEncoder = (_c = options == null ? void 0 : options.stateEncoder) != null ? _c : async (req) => {
1250
+ return { encodedState: encodeState(req.state) };
1251
+ };
1252
+ const provider = new GithubAuthProvider({
1123
1253
  clientId,
1124
1254
  clientSecret,
1125
1255
  callbackUrl,
1126
- authorizationUrl,
1127
1256
  tokenUrl,
1128
- authHandler,
1257
+ userProfileUrl,
1258
+ authorizationUrl,
1129
1259
  signInResolver,
1260
+ authHandler,
1261
+ tokenIssuer,
1130
1262
  catalogIdentityClient,
1131
- logger,
1132
- tokenIssuer
1263
+ stateEncoder,
1264
+ logger
1133
1265
  });
1134
1266
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1135
- disableRefresh: false,
1267
+ persistScopes: true,
1136
1268
  providerId,
1137
1269
  tokenIssuer
1138
1270
  });
1139
1271
  });
1140
1272
  };
1141
1273
 
1142
- class OAuth2AuthProvider {
1274
+ const gitlabDefaultSignInResolver = async (info, ctx) => {
1275
+ const { profile, result } = info;
1276
+ let id = result.fullProfile.id;
1277
+ if (profile.email) {
1278
+ id = profile.email.split("@")[0];
1279
+ }
1280
+ const token = await ctx.tokenIssuer.issueToken({
1281
+ claims: { sub: id, ent: [`user:default/${id}`] }
1282
+ });
1283
+ return { id, token };
1284
+ };
1285
+ const gitlabDefaultAuthHandler = async ({
1286
+ fullProfile,
1287
+ params
1288
+ }) => ({
1289
+ profile: makeProfileInfo(fullProfile, params.id_token)
1290
+ });
1291
+ class GitlabAuthProvider {
1143
1292
  constructor(options) {
1144
- this.signInResolver = options.signInResolver;
1145
- this.authHandler = options.authHandler;
1146
- this.tokenIssuer = options.tokenIssuer;
1147
1293
  this.catalogIdentityClient = options.catalogIdentityClient;
1148
1294
  this.logger = options.logger;
1149
- this._strategy = new OAuth2Strategy.Strategy({
1295
+ this.tokenIssuer = options.tokenIssuer;
1296
+ this.authHandler = options.authHandler;
1297
+ this.signInResolver = options.signInResolver;
1298
+ this._strategy = new passportGitlab2.Strategy({
1150
1299
  clientID: options.clientId,
1151
1300
  clientSecret: options.clientSecret,
1152
1301
  callbackURL: options.callbackUrl,
1153
- authorizationURL: options.authorizationUrl,
1154
- tokenURL: options.tokenUrl,
1155
- passReqToCallback: false,
1156
- scope: options.scope,
1157
- customHeaders: options.includeBasicAuth ? {
1158
- Authorization: `Basic ${this.encodeClientCredentials(options.clientId, options.clientSecret)}`
1159
- } : void 0
1302
+ baseURL: options.baseUrl
1160
1303
  }, (accessToken, refreshToken, params, fullProfile, done) => {
1161
- done(void 0, {
1162
- fullProfile,
1163
- accessToken,
1164
- refreshToken,
1165
- params
1166
- }, {
1304
+ done(void 0, { fullProfile, params, accessToken }, {
1167
1305
  refreshToken
1168
1306
  });
1169
1307
  });
1170
1308
  }
1171
1309
  async start(req) {
1172
1310
  return await executeRedirectStrategy(req, this._strategy, {
1173
- accessType: "offline",
1174
- prompt: "consent",
1175
1311
  scope: req.scope,
1176
1312
  state: encodeState(req.state)
1177
1313
  });
@@ -1184,29 +1320,30 @@ class OAuth2AuthProvider {
1184
1320
  };
1185
1321
  }
1186
1322
  async refresh(req) {
1187
- const refreshTokenResponse = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1188
- const {
1189
- accessToken,
1190
- params,
1191
- refreshToken: updatedRefreshToken
1192
- } = refreshTokenResponse;
1323
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1193
1324
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1194
- return this.handleResult({
1195
- fullProfile,
1196
- params,
1197
- accessToken,
1198
- refreshToken: updatedRefreshToken
1199
- });
1325
+ return {
1326
+ response: await this.handleResult({
1327
+ fullProfile,
1328
+ params,
1329
+ accessToken
1330
+ }),
1331
+ refreshToken
1332
+ };
1200
1333
  }
1201
1334
  async handleResult(result) {
1202
- const { profile } = await this.authHandler(result);
1335
+ const context = {
1336
+ logger: this.logger,
1337
+ catalogIdentityClient: this.catalogIdentityClient,
1338
+ tokenIssuer: this.tokenIssuer
1339
+ };
1340
+ const { profile } = await this.authHandler(result, context);
1203
1341
  const response = {
1204
1342
  providerInfo: {
1205
1343
  idToken: result.params.id_token,
1206
1344
  accessToken: result.accessToken,
1207
1345
  scope: result.params.scope,
1208
- expiresInSeconds: result.params.expires_in,
1209
- refreshToken: result.refreshToken
1346
+ expiresInSeconds: result.params.expires_in
1210
1347
  },
1211
1348
  profile
1212
1349
  };
@@ -1214,30 +1351,12 @@ class OAuth2AuthProvider {
1214
1351
  response.backstageIdentity = await this.signInResolver({
1215
1352
  result,
1216
1353
  profile
1217
- }, {
1218
- tokenIssuer: this.tokenIssuer,
1219
- catalogIdentityClient: this.catalogIdentityClient,
1220
- logger: this.logger
1221
- });
1354
+ }, context);
1222
1355
  }
1223
1356
  return response;
1224
1357
  }
1225
- encodeClientCredentials(clientID, clientSecret) {
1226
- return Buffer.from(`${clientID}:${clientSecret}`).toString("base64");
1227
- }
1228
1358
  }
1229
- const oAuth2DefaultSignInResolver$1 = async (info, ctx) => {
1230
- const { profile } = info;
1231
- if (!profile.email) {
1232
- throw new Error("Profile contained no email");
1233
- }
1234
- const userId = profile.email.split("@")[0];
1235
- const token = await ctx.tokenIssuer.issueToken({
1236
- claims: { sub: userId, ent: [`user:default/${userId}`] }
1237
- });
1238
- return { id: userId, token };
1239
- };
1240
- const createOAuth2Provider = (options) => {
1359
+ const createGitlabProvider = (options) => {
1241
1360
  return ({
1242
1361
  providerId,
1243
1362
  globalConfig,
@@ -1249,126 +1368,102 @@ const createOAuth2Provider = (options) => {
1249
1368
  var _a, _b, _c;
1250
1369
  const clientId = envConfig.getString("clientId");
1251
1370
  const clientSecret = envConfig.getString("clientSecret");
1371
+ const audience = envConfig.getOptionalString("audience");
1372
+ const baseUrl = audience || "https://gitlab.com";
1252
1373
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1253
- const authorizationUrl = envConfig.getString("authorizationUrl");
1254
- const tokenUrl = envConfig.getString("tokenUrl");
1255
- const scope = envConfig.getOptionalString("scope");
1256
- const includeBasicAuth = envConfig.getOptionalBoolean("includeBasicAuth");
1257
- const disableRefresh = (_a = envConfig.getOptionalBoolean("disableRefresh")) != null ? _a : false;
1258
1374
  const catalogIdentityClient = new CatalogIdentityClient({
1259
1375
  catalogApi,
1260
1376
  tokenIssuer
1261
1377
  });
1262
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
1263
- profile: makeProfileInfo(fullProfile, params.id_token)
1264
- });
1265
- const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : oAuth2DefaultSignInResolver$1;
1378
+ const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : gitlabDefaultAuthHandler;
1379
+ const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : gitlabDefaultSignInResolver;
1266
1380
  const signInResolver = (info) => signInResolverFn(info, {
1267
1381
  catalogIdentityClient,
1268
1382
  tokenIssuer,
1269
1383
  logger
1270
1384
  });
1271
- const provider = new OAuth2AuthProvider({
1385
+ const provider = new GitlabAuthProvider({
1272
1386
  clientId,
1273
1387
  clientSecret,
1274
- tokenIssuer,
1275
- catalogIdentityClient,
1276
1388
  callbackUrl,
1277
- signInResolver,
1389
+ baseUrl,
1278
1390
  authHandler,
1279
- authorizationUrl,
1280
- tokenUrl,
1281
- scope,
1391
+ signInResolver,
1392
+ catalogIdentityClient,
1282
1393
  logger,
1283
- includeBasicAuth
1394
+ tokenIssuer
1284
1395
  });
1285
1396
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1286
- disableRefresh,
1397
+ disableRefresh: false,
1287
1398
  providerId,
1288
1399
  tokenIssuer
1289
1400
  });
1290
1401
  });
1291
1402
  };
1292
1403
 
1293
- class OidcAuthProvider {
1404
+ class GoogleAuthProvider {
1294
1405
  constructor(options) {
1295
- this.implementation = this.setupStrategy(options);
1296
- this.scope = options.scope;
1297
- this.prompt = options.prompt;
1298
1406
  this.signInResolver = options.signInResolver;
1299
1407
  this.authHandler = options.authHandler;
1300
1408
  this.tokenIssuer = options.tokenIssuer;
1301
1409
  this.catalogIdentityClient = options.catalogIdentityClient;
1302
1410
  this.logger = options.logger;
1411
+ this._strategy = new passportGoogleOauth20.Strategy({
1412
+ clientID: options.clientId,
1413
+ clientSecret: options.clientSecret,
1414
+ callbackURL: options.callbackUrl,
1415
+ passReqToCallback: false
1416
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
1417
+ done(void 0, {
1418
+ fullProfile,
1419
+ params,
1420
+ accessToken,
1421
+ refreshToken
1422
+ }, {
1423
+ refreshToken
1424
+ });
1425
+ });
1303
1426
  }
1304
1427
  async start(req) {
1305
- const { strategy } = await this.implementation;
1306
- const options = {
1307
- scope: req.scope || this.scope || "openid profile email",
1428
+ return await executeRedirectStrategy(req, this._strategy, {
1429
+ accessType: "offline",
1430
+ prompt: "consent",
1431
+ scope: req.scope,
1308
1432
  state: encodeState(req.state)
1309
- };
1310
- const prompt = this.prompt || "none";
1311
- if (prompt !== "auto") {
1312
- options.prompt = prompt;
1313
- }
1314
- return await executeRedirectStrategy(req, strategy, options);
1433
+ });
1315
1434
  }
1316
1435
  async handler(req) {
1317
- const { strategy } = await this.implementation;
1318
- const strategyResponse = await executeFrameHandlerStrategy(req, strategy);
1319
- const {
1320
- result: { userinfo, tokenset },
1321
- privateInfo
1322
- } = strategyResponse;
1323
- const identityResponse = await this.handleResult({ tokenset, userinfo });
1436
+ const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
1324
1437
  return {
1325
- response: identityResponse,
1438
+ response: await this.handleResult(result),
1326
1439
  refreshToken: privateInfo.refreshToken
1327
1440
  };
1328
1441
  }
1329
1442
  async refresh(req) {
1330
- const { client } = await this.implementation;
1331
- const tokenset = await client.refresh(req.refreshToken);
1332
- if (!tokenset.access_token) {
1333
- throw new Error("Refresh failed");
1334
- }
1335
- const profile = await client.userinfo(tokenset.access_token);
1336
- return this.handleResult({ tokenset, userinfo: profile });
1337
- }
1338
- async setupStrategy(options) {
1339
- const issuer = await openidClient.Issuer.discover(options.metadataUrl);
1340
- const client = new issuer.Client({
1341
- access_type: "offline",
1342
- client_id: options.clientId,
1343
- client_secret: options.clientSecret,
1344
- redirect_uris: [options.callbackUrl],
1345
- response_types: ["code"],
1346
- id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
1347
- scope: options.scope || ""
1348
- });
1349
- const strategy = new openidClient.Strategy({
1350
- client,
1351
- passReqToCallback: false
1352
- }, (tokenset, userinfo, done) => {
1353
- if (typeof done !== "function") {
1354
- throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
1355
- }
1356
- done(void 0, { tokenset, userinfo }, {
1357
- refreshToken: tokenset.refresh_token
1358
- });
1359
- });
1360
- strategy.error = console.error;
1361
- return { strategy, client };
1443
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1444
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1445
+ return {
1446
+ response: await this.handleResult({
1447
+ fullProfile,
1448
+ params,
1449
+ accessToken
1450
+ }),
1451
+ refreshToken
1452
+ };
1362
1453
  }
1363
1454
  async handleResult(result) {
1364
- const { profile } = await this.authHandler(result);
1455
+ const context = {
1456
+ logger: this.logger,
1457
+ catalogIdentityClient: this.catalogIdentityClient,
1458
+ tokenIssuer: this.tokenIssuer
1459
+ };
1460
+ const { profile } = await this.authHandler(result, context);
1365
1461
  const response = {
1366
1462
  providerInfo: {
1367
- idToken: result.tokenset.id_token,
1368
- accessToken: result.tokenset.access_token,
1369
- refreshToken: result.tokenset.refresh_token,
1370
- scope: result.tokenset.scope,
1371
- expiresInSeconds: result.tokenset.expires_in
1463
+ idToken: result.params.id_token,
1464
+ accessToken: result.accessToken,
1465
+ scope: result.params.scope,
1466
+ expiresInSeconds: result.params.expires_in
1372
1467
  },
1373
1468
  profile
1374
1469
  };
@@ -1376,27 +1471,48 @@ class OidcAuthProvider {
1376
1471
  response.backstageIdentity = await this.signInResolver({
1377
1472
  result,
1378
1473
  profile
1379
- }, {
1380
- tokenIssuer: this.tokenIssuer,
1381
- catalogIdentityClient: this.catalogIdentityClient,
1382
- logger: this.logger
1383
- });
1474
+ }, context);
1384
1475
  }
1385
1476
  return response;
1386
1477
  }
1387
1478
  }
1388
- const oAuth2DefaultSignInResolver = async (info, ctx) => {
1479
+ const googleEmailSignInResolver = async (info, ctx) => {
1389
1480
  const { profile } = info;
1390
1481
  if (!profile.email) {
1391
- throw new Error("Profile contained no email");
1482
+ throw new Error("Google profile contained no email");
1483
+ }
1484
+ const entity = await ctx.catalogIdentityClient.findUser({
1485
+ annotations: {
1486
+ "google.com/email": profile.email
1487
+ }
1488
+ });
1489
+ const claims = getEntityClaims(entity);
1490
+ const token = await ctx.tokenIssuer.issueToken({ claims });
1491
+ return { id: entity.metadata.name, entity, token };
1492
+ };
1493
+ const googleDefaultSignInResolver = async (info, ctx) => {
1494
+ const { profile } = info;
1495
+ if (!profile.email) {
1496
+ throw new Error("Google profile contained no email");
1497
+ }
1498
+ let userId;
1499
+ try {
1500
+ const entity = await ctx.catalogIdentityClient.findUser({
1501
+ annotations: {
1502
+ "google.com/email": profile.email
1503
+ }
1504
+ });
1505
+ userId = entity.metadata.name;
1506
+ } catch (error) {
1507
+ ctx.logger.warn(`Failed to look up user, ${error}, falling back to allowing login based on email pattern, this will probably break in the future`);
1508
+ userId = profile.email.split("@")[0];
1392
1509
  }
1393
- const userId = profile.email.split("@")[0];
1394
1510
  const token = await ctx.tokenIssuer.issueToken({
1395
1511
  claims: { sub: userId, ent: [`user:default/${userId}`] }
1396
1512
  });
1397
1513
  return { id: userId, token };
1398
1514
  };
1399
- const createOidcProvider = (options) => {
1515
+ const createGoogleProvider = (options) => {
1400
1516
  return ({
1401
1517
  providerId,
1402
1518
  globalConfig,
@@ -1409,40 +1525,28 @@ const createOidcProvider = (options) => {
1409
1525
  const clientId = envConfig.getString("clientId");
1410
1526
  const clientSecret = envConfig.getString("clientSecret");
1411
1527
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1412
- const metadataUrl = envConfig.getString("metadataUrl");
1413
- const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
1414
- const scope = envConfig.getOptionalString("scope");
1415
- const prompt = envConfig.getOptionalString("prompt");
1416
1528
  const catalogIdentityClient = new CatalogIdentityClient({
1417
1529
  catalogApi,
1418
1530
  tokenIssuer
1419
1531
  });
1420
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ userinfo }) => ({
1421
- profile: {
1422
- displayName: userinfo.name,
1423
- email: userinfo.email,
1424
- picture: userinfo.picture
1425
- }
1532
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
1533
+ profile: makeProfileInfo(fullProfile, params.id_token)
1426
1534
  });
1427
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oAuth2DefaultSignInResolver;
1535
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : googleDefaultSignInResolver;
1428
1536
  const signInResolver = (info) => signInResolverFn(info, {
1429
1537
  catalogIdentityClient,
1430
1538
  tokenIssuer,
1431
1539
  logger
1432
1540
  });
1433
- const provider = new OidcAuthProvider({
1541
+ const provider = new GoogleAuthProvider({
1434
1542
  clientId,
1435
1543
  clientSecret,
1436
1544
  callbackUrl,
1437
- tokenSignedResponseAlg,
1438
- metadataUrl,
1439
- scope,
1440
- prompt,
1441
1545
  signInResolver,
1442
1546
  authHandler,
1443
- logger,
1444
1547
  tokenIssuer,
1445
- catalogIdentityClient
1548
+ catalogIdentityClient,
1549
+ logger
1446
1550
  });
1447
1551
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1448
1552
  disableRefresh: false,
@@ -1452,44 +1556,26 @@ const createOidcProvider = (options) => {
1452
1556
  });
1453
1557
  };
1454
1558
 
1455
- class OktaAuthProvider {
1559
+ class MicrosoftAuthProvider {
1456
1560
  constructor(options) {
1457
- this._store = {
1458
- store(_req, cb) {
1459
- cb(null, null);
1460
- },
1461
- verify(_req, _state, cb) {
1462
- cb(null, true);
1463
- }
1464
- };
1465
- this._signInResolver = options.signInResolver;
1466
- this._authHandler = options.authHandler;
1467
- this._tokenIssuer = options.tokenIssuer;
1468
- this._catalogIdentityClient = options.catalogIdentityClient;
1469
- this._logger = options.logger;
1470
- this._strategy = new passportOktaOauth.Strategy({
1561
+ this.signInResolver = options.signInResolver;
1562
+ this.authHandler = options.authHandler;
1563
+ this.tokenIssuer = options.tokenIssuer;
1564
+ this.logger = options.logger;
1565
+ this.catalogIdentityClient = options.catalogIdentityClient;
1566
+ this._strategy = new passportMicrosoft.Strategy({
1471
1567
  clientID: options.clientId,
1472
1568
  clientSecret: options.clientSecret,
1473
1569
  callbackURL: options.callbackUrl,
1474
- audience: options.audience,
1475
- passReqToCallback: false,
1476
- store: this._store,
1477
- response_type: "code"
1570
+ authorizationURL: options.authorizationUrl,
1571
+ tokenURL: options.tokenUrl,
1572
+ passReqToCallback: false
1478
1573
  }, (accessToken, refreshToken, params, fullProfile, done) => {
1479
- done(void 0, {
1480
- accessToken,
1481
- refreshToken,
1482
- params,
1483
- fullProfile
1484
- }, {
1485
- refreshToken
1486
- });
1574
+ done(void 0, { fullProfile, accessToken, params }, { refreshToken });
1487
1575
  });
1488
1576
  }
1489
1577
  async start(req) {
1490
1578
  return await executeRedirectStrategy(req, this._strategy, {
1491
- accessType: "offline",
1492
- prompt: "consent",
1493
1579
  scope: req.scope,
1494
1580
  state: encodeState(req.state)
1495
1581
  });
@@ -1502,17 +1588,26 @@ class OktaAuthProvider {
1502
1588
  };
1503
1589
  }
1504
1590
  async refresh(req) {
1505
- const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1591
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1506
1592
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1507
- return this.handleResult({
1508
- fullProfile,
1509
- params,
1510
- accessToken,
1511
- refreshToken: req.refreshToken
1512
- });
1593
+ return {
1594
+ response: await this.handleResult({
1595
+ fullProfile,
1596
+ params,
1597
+ accessToken
1598
+ }),
1599
+ refreshToken
1600
+ };
1513
1601
  }
1514
1602
  async handleResult(result) {
1515
- const { profile } = await this._authHandler(result);
1603
+ const photo = await this.getUserPhoto(result.accessToken);
1604
+ result.fullProfile.photos = photo ? [{ value: photo }] : void 0;
1605
+ const context = {
1606
+ logger: this.logger,
1607
+ catalogIdentityClient: this.catalogIdentityClient,
1608
+ tokenIssuer: this.tokenIssuer
1609
+ };
1610
+ const { profile } = await this.authHandler(result, context);
1516
1611
  const response = {
1517
1612
  providerInfo: {
1518
1613
  idToken: result.params.id_token,
@@ -1522,37 +1617,48 @@ class OktaAuthProvider {
1522
1617
  },
1523
1618
  profile
1524
1619
  };
1525
- if (this._signInResolver) {
1526
- response.backstageIdentity = await this._signInResolver({
1620
+ if (this.signInResolver) {
1621
+ response.backstageIdentity = await this.signInResolver({
1527
1622
  result,
1528
1623
  profile
1529
- }, {
1530
- tokenIssuer: this._tokenIssuer,
1531
- catalogIdentityClient: this._catalogIdentityClient,
1532
- logger: this._logger
1533
- });
1624
+ }, context);
1534
1625
  }
1535
1626
  return response;
1536
1627
  }
1628
+ getUserPhoto(accessToken) {
1629
+ return new Promise((resolve) => {
1630
+ fetch__default["default"]("https://graph.microsoft.com/v1.0/me/photos/48x48/$value", {
1631
+ headers: {
1632
+ Authorization: `Bearer ${accessToken}`
1633
+ }
1634
+ }).then((response) => response.arrayBuffer()).then((arrayBuffer) => {
1635
+ const imageUrl = `data:image/jpeg;base64,${Buffer.from(arrayBuffer).toString("base64")}`;
1636
+ resolve(imageUrl);
1637
+ }).catch((error) => {
1638
+ this.logger.warn(`Could not retrieve user profile photo from Microsoft Graph API: ${error}`);
1639
+ resolve(void 0);
1640
+ });
1641
+ });
1642
+ }
1537
1643
  }
1538
- const oktaEmailSignInResolver = async (info, ctx) => {
1644
+ const microsoftEmailSignInResolver = async (info, ctx) => {
1539
1645
  const { profile } = info;
1540
1646
  if (!profile.email) {
1541
- throw new Error("Okta profile contained no email");
1647
+ throw new Error("Microsoft profile contained no email");
1542
1648
  }
1543
1649
  const entity = await ctx.catalogIdentityClient.findUser({
1544
1650
  annotations: {
1545
- "okta.com/email": profile.email
1651
+ "microsoft.com/email": profile.email
1546
1652
  }
1547
1653
  });
1548
1654
  const claims = getEntityClaims(entity);
1549
1655
  const token = await ctx.tokenIssuer.issueToken({ claims });
1550
1656
  return { id: entity.metadata.name, entity, token };
1551
1657
  };
1552
- const oktaDefaultSignInResolver = async (info, ctx) => {
1658
+ const microsoftDefaultSignInResolver = async (info, ctx) => {
1553
1659
  const { profile } = info;
1554
1660
  if (!profile.email) {
1555
- throw new Error("Okta profile contained no email");
1661
+ throw new Error("Profile contained no email");
1556
1662
  }
1557
1663
  const userId = profile.email.split("@")[0];
1558
1664
  const token = await ctx.tokenIssuer.issueToken({
@@ -1560,7 +1666,7 @@ const oktaDefaultSignInResolver = async (info, ctx) => {
1560
1666
  });
1561
1667
  return { id: userId, token };
1562
1668
  };
1563
- const createOktaProvider = (_options) => {
1669
+ const createMicrosoftProvider = (options) => {
1564
1670
  return ({
1565
1671
  providerId,
1566
1672
  globalConfig,
@@ -1572,34 +1678,34 @@ const createOktaProvider = (_options) => {
1572
1678
  var _a, _b;
1573
1679
  const clientId = envConfig.getString("clientId");
1574
1680
  const clientSecret = envConfig.getString("clientSecret");
1575
- const audience = envConfig.getString("audience");
1681
+ const tenantId = envConfig.getString("tenantId");
1576
1682
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1577
- if (!audience.startsWith("https://")) {
1578
- throw new Error("URL for 'audience' must start with 'https://'.");
1579
- }
1683
+ const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
1684
+ const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
1580
1685
  const catalogIdentityClient = new CatalogIdentityClient({
1581
1686
  catalogApi,
1582
1687
  tokenIssuer
1583
1688
  });
1584
- const authHandler = (_options == null ? void 0 : _options.authHandler) ? _options.authHandler : async ({ fullProfile, params }) => ({
1689
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
1585
1690
  profile: makeProfileInfo(fullProfile, params.id_token)
1586
1691
  });
1587
- const signInResolverFn = (_b = (_a = _options == null ? void 0 : _options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oktaDefaultSignInResolver;
1692
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : microsoftDefaultSignInResolver;
1588
1693
  const signInResolver = (info) => signInResolverFn(info, {
1589
1694
  catalogIdentityClient,
1590
1695
  tokenIssuer,
1591
1696
  logger
1592
1697
  });
1593
- const provider = new OktaAuthProvider({
1594
- audience,
1698
+ const provider = new MicrosoftAuthProvider({
1595
1699
  clientId,
1596
1700
  clientSecret,
1597
1701
  callbackUrl,
1702
+ authorizationUrl,
1703
+ tokenUrl,
1598
1704
  authHandler,
1599
1705
  signInResolver,
1600
- tokenIssuer,
1601
1706
  catalogIdentityClient,
1602
- logger
1707
+ logger,
1708
+ tokenIssuer
1603
1709
  });
1604
1710
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1605
1711
  disableRefresh: false,
@@ -1609,24 +1715,30 @@ const createOktaProvider = (_options) => {
1609
1715
  });
1610
1716
  };
1611
1717
 
1612
- class BitbucketAuthProvider {
1718
+ class OAuth2AuthProvider {
1613
1719
  constructor(options) {
1614
1720
  this.signInResolver = options.signInResolver;
1615
1721
  this.authHandler = options.authHandler;
1616
1722
  this.tokenIssuer = options.tokenIssuer;
1617
1723
  this.catalogIdentityClient = options.catalogIdentityClient;
1618
1724
  this.logger = options.logger;
1619
- this._strategy = new passportBitbucketOauth2.Strategy({
1725
+ this._strategy = new OAuth2Strategy.Strategy({
1620
1726
  clientID: options.clientId,
1621
1727
  clientSecret: options.clientSecret,
1622
1728
  callbackURL: options.callbackUrl,
1623
- passReqToCallback: false
1729
+ authorizationURL: options.authorizationUrl,
1730
+ tokenURL: options.tokenUrl,
1731
+ passReqToCallback: false,
1732
+ scope: options.scope,
1733
+ customHeaders: options.includeBasicAuth ? {
1734
+ Authorization: `Basic ${this.encodeClientCredentials(options.clientId, options.clientSecret)}`
1735
+ } : void 0
1624
1736
  }, (accessToken, refreshToken, params, fullProfile, done) => {
1625
1737
  done(void 0, {
1626
1738
  fullProfile,
1627
- params,
1628
1739
  accessToken,
1629
- refreshToken
1740
+ refreshToken,
1741
+ params
1630
1742
  }, {
1631
1743
  refreshToken
1632
1744
  });
@@ -1648,18 +1760,25 @@ class BitbucketAuthProvider {
1648
1760
  };
1649
1761
  }
1650
1762
  async refresh(req) {
1651
- const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1763
+ const refreshTokenResponse = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1764
+ const { accessToken, params, refreshToken } = refreshTokenResponse;
1652
1765
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1653
- return this.handleResult({
1654
- fullProfile,
1655
- params,
1656
- accessToken,
1657
- refreshToken: req.refreshToken
1658
- });
1766
+ return {
1767
+ response: await this.handleResult({
1768
+ fullProfile,
1769
+ params,
1770
+ accessToken
1771
+ }),
1772
+ refreshToken
1773
+ };
1659
1774
  }
1660
1775
  async handleResult(result) {
1661
- result.fullProfile.avatarUrl = result.fullProfile._json.links.avatar.href;
1662
- const { profile } = await this.authHandler(result);
1776
+ const context = {
1777
+ logger: this.logger,
1778
+ catalogIdentityClient: this.catalogIdentityClient,
1779
+ tokenIssuer: this.tokenIssuer
1780
+ };
1781
+ const { profile } = await this.authHandler(result, context);
1663
1782
  const response = {
1664
1783
  providerInfo: {
1665
1784
  idToken: result.params.id_token,
@@ -1673,44 +1792,26 @@ class BitbucketAuthProvider {
1673
1792
  response.backstageIdentity = await this.signInResolver({
1674
1793
  result,
1675
1794
  profile
1676
- }, {
1677
- tokenIssuer: this.tokenIssuer,
1678
- catalogIdentityClient: this.catalogIdentityClient,
1679
- logger: this.logger
1680
- });
1795
+ }, context);
1681
1796
  }
1682
1797
  return response;
1683
1798
  }
1799
+ encodeClientCredentials(clientID, clientSecret) {
1800
+ return Buffer.from(`${clientID}:${clientSecret}`).toString("base64");
1801
+ }
1684
1802
  }
1685
- const bitbucketUsernameSignInResolver = async (info, ctx) => {
1686
- const { result } = info;
1687
- if (!result.fullProfile.username) {
1688
- throw new Error("Bitbucket profile contained no Username");
1803
+ const oAuth2DefaultSignInResolver$1 = async (info, ctx) => {
1804
+ const { profile } = info;
1805
+ if (!profile.email) {
1806
+ throw new Error("Profile contained no email");
1689
1807
  }
1690
- const entity = await ctx.catalogIdentityClient.findUser({
1691
- annotations: {
1692
- "bitbucket.org/username": result.fullProfile.username
1693
- }
1694
- });
1695
- const claims = getEntityClaims(entity);
1696
- const token = await ctx.tokenIssuer.issueToken({ claims });
1697
- return { id: entity.metadata.name, entity, token };
1698
- };
1699
- const bitbucketUserIdSignInResolver = async (info, ctx) => {
1700
- const { result } = info;
1701
- if (!result.fullProfile.id) {
1702
- throw new Error("Bitbucket profile contained no User ID");
1703
- }
1704
- const entity = await ctx.catalogIdentityClient.findUser({
1705
- annotations: {
1706
- "bitbucket.org/user-id": result.fullProfile.id
1707
- }
1808
+ const userId = profile.email.split("@")[0];
1809
+ const token = await ctx.tokenIssuer.issueToken({
1810
+ claims: { sub: userId, ent: [`user:default/${userId}`] }
1708
1811
  });
1709
- const claims = getEntityClaims(entity);
1710
- const token = await ctx.tokenIssuer.issueToken({ claims });
1711
- return { id: entity.metadata.name, entity, token };
1812
+ return { id: userId, token };
1712
1813
  };
1713
- const createBitbucketProvider = (options) => {
1814
+ const createOAuth2Provider = (options) => {
1714
1815
  return ({
1715
1816
  providerId,
1716
1817
  globalConfig,
@@ -1719,10 +1820,15 @@ const createBitbucketProvider = (options) => {
1719
1820
  catalogApi,
1720
1821
  logger
1721
1822
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1722
- var _a;
1823
+ var _a, _b, _c;
1723
1824
  const clientId = envConfig.getString("clientId");
1724
1825
  const clientSecret = envConfig.getString("clientSecret");
1725
1826
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1827
+ const authorizationUrl = envConfig.getString("authorizationUrl");
1828
+ const tokenUrl = envConfig.getString("tokenUrl");
1829
+ const scope = envConfig.getOptionalString("scope");
1830
+ const includeBasicAuth = envConfig.getOptionalBoolean("includeBasicAuth");
1831
+ const disableRefresh = (_a = envConfig.getOptionalBoolean("disableRefresh")) != null ? _a : false;
1726
1832
  const catalogIdentityClient = new CatalogIdentityClient({
1727
1833
  catalogApi,
1728
1834
  tokenIssuer
@@ -1730,222 +1836,393 @@ const createBitbucketProvider = (options) => {
1730
1836
  const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
1731
1837
  profile: makeProfileInfo(fullProfile, params.id_token)
1732
1838
  });
1733
- const provider = new BitbucketAuthProvider({
1839
+ const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : oAuth2DefaultSignInResolver$1;
1840
+ const signInResolver = (info) => signInResolverFn(info, {
1841
+ catalogIdentityClient,
1842
+ tokenIssuer,
1843
+ logger
1844
+ });
1845
+ const provider = new OAuth2AuthProvider({
1734
1846
  clientId,
1735
1847
  clientSecret,
1736
- callbackUrl,
1737
- signInResolver: (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver,
1738
- authHandler,
1739
1848
  tokenIssuer,
1740
1849
  catalogIdentityClient,
1741
- logger
1850
+ callbackUrl,
1851
+ signInResolver,
1852
+ authHandler,
1853
+ authorizationUrl,
1854
+ tokenUrl,
1855
+ scope,
1856
+ logger,
1857
+ includeBasicAuth
1742
1858
  });
1743
1859
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1744
- disableRefresh: false,
1860
+ disableRefresh,
1745
1861
  providerId,
1746
1862
  tokenIssuer
1747
1863
  });
1748
1864
  });
1749
1865
  };
1750
1866
 
1751
- const defaultScopes = ["offline_access", "read:me"];
1752
- class AtlassianStrategy extends OAuth2Strategy__default["default"] {
1753
- constructor(options, verify) {
1754
- if (!options.scope) {
1755
- throw new TypeError("Atlassian requires a scope option");
1867
+ function createOidcRouter(options) {
1868
+ const { baseUrl, tokenIssuer } = options;
1869
+ const router = Router__default["default"]();
1870
+ const config = {
1871
+ issuer: baseUrl,
1872
+ token_endpoint: `${baseUrl}/v1/token`,
1873
+ userinfo_endpoint: `${baseUrl}/v1/userinfo`,
1874
+ jwks_uri: `${baseUrl}/.well-known/jwks.json`,
1875
+ response_types_supported: ["id_token"],
1876
+ subject_types_supported: ["public"],
1877
+ id_token_signing_alg_values_supported: ["RS256"],
1878
+ scopes_supported: ["openid"],
1879
+ token_endpoint_auth_methods_supported: [],
1880
+ claims_supported: ["sub"],
1881
+ grant_types_supported: []
1882
+ };
1883
+ router.get("/.well-known/openid-configuration", (_req, res) => {
1884
+ res.json(config);
1885
+ });
1886
+ router.get("/.well-known/jwks.json", async (_req, res) => {
1887
+ const { keys } = await tokenIssuer.listPublicKeys();
1888
+ res.json({ keys });
1889
+ });
1890
+ router.get("/v1/token", (_req, res) => {
1891
+ res.status(501).send("Not Implemented");
1892
+ });
1893
+ router.get("/v1/userinfo", (_req, res) => {
1894
+ res.status(501).send("Not Implemented");
1895
+ });
1896
+ return router;
1897
+ }
1898
+
1899
+ const CLOCK_MARGIN_S = 10;
1900
+ class IdentityClient {
1901
+ constructor(options) {
1902
+ this.discovery = options.discovery;
1903
+ this.issuer = options.issuer;
1904
+ this.keyStore = new jose.JWKS.KeyStore();
1905
+ this.keyStoreUpdated = 0;
1906
+ }
1907
+ async authenticate(token) {
1908
+ var _a;
1909
+ if (!token) {
1910
+ throw new errors.AuthenticationError("No token specified");
1756
1911
  }
1757
- const scopes = options.scope.split(" ");
1758
- const optionsWithURLs = {
1759
- ...options,
1760
- authorizationURL: `https://auth.atlassian.com/authorize`,
1761
- tokenURL: `https://auth.atlassian.com/oauth/token`,
1762
- scope: Array.from(/* @__PURE__ */ new Set([...defaultScopes, ...scopes]))
1912
+ const key = await this.getKey(token);
1913
+ if (!key) {
1914
+ throw new errors.AuthenticationError("No signing key matching token found");
1915
+ }
1916
+ const decoded = jose.JWT.IdToken.verify(token, key, {
1917
+ algorithms: ["ES256"],
1918
+ audience: "backstage",
1919
+ issuer: this.issuer
1920
+ });
1921
+ if (!decoded.sub) {
1922
+ throw new errors.AuthenticationError("No user sub found in token");
1923
+ }
1924
+ const user = {
1925
+ id: decoded.sub,
1926
+ token,
1927
+ identity: {
1928
+ type: "user",
1929
+ userEntityRef: decoded.sub,
1930
+ ownershipEntityRefs: (_a = decoded.ent) != null ? _a : []
1931
+ }
1763
1932
  };
1764
- super(optionsWithURLs, verify);
1765
- this.profileURL = "https://api.atlassian.com/me";
1766
- this.name = "atlassian";
1767
- this._oauth2.useAuthorizationHeaderforGET(true);
1933
+ return user;
1768
1934
  }
1769
- authorizationParams() {
1770
- return {
1771
- audience: "api.atlassian.com",
1772
- prompt: "consent"
1773
- };
1935
+ static getBearerToken(authorizationHeader) {
1936
+ if (typeof authorizationHeader !== "string") {
1937
+ return void 0;
1938
+ }
1939
+ const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
1940
+ return matches == null ? void 0 : matches[1];
1774
1941
  }
1775
- userProfile(accessToken, done) {
1776
- this._oauth2.get(this.profileURL, accessToken, (err, body) => {
1777
- if (err) {
1778
- return done(new OAuth2Strategy.InternalOAuthError("Failed to fetch user profile", err.statusCode));
1779
- }
1780
- if (!body) {
1781
- return done(new Error("Failed to fetch user profile, body cannot be empty"));
1782
- }
1783
- try {
1784
- const json = typeof body !== "string" ? body.toString() : body;
1785
- const profile = AtlassianStrategy.parse(json);
1786
- return done(null, profile);
1787
- } catch (e) {
1788
- return done(new Error("Failed to parse user profile"));
1789
- }
1942
+ async getKey(rawJwtToken) {
1943
+ const { header, payload } = jose.JWT.decode(rawJwtToken, {
1944
+ complete: true
1790
1945
  });
1946
+ const keyStoreHasKey = !!this.keyStore.get({ kid: header.kid });
1947
+ const issuedAfterLastRefresh = (payload == null ? void 0 : payload.iat) && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
1948
+ if (!keyStoreHasKey && issuedAfterLastRefresh) {
1949
+ await this.refreshKeyStore();
1950
+ }
1951
+ return this.keyStore.get({ kid: header.kid });
1791
1952
  }
1792
- static parse(json) {
1793
- const resp = JSON.parse(json);
1794
- return {
1795
- id: resp.account_id,
1796
- provider: "atlassian",
1797
- username: resp.nickname,
1798
- displayName: resp.name,
1799
- emails: [{ value: resp.email }],
1800
- photos: [{ value: resp.picture }]
1801
- };
1953
+ async listPublicKeys() {
1954
+ const url = `${await this.discovery.getBaseUrl("auth")}/.well-known/jwks.json`;
1955
+ const response = await fetch__default["default"](url);
1956
+ if (!response.ok) {
1957
+ const payload = await response.text();
1958
+ const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
1959
+ throw new Error(message);
1960
+ }
1961
+ const publicKeys = await response.json();
1962
+ return publicKeys;
1963
+ }
1964
+ async refreshKeyStore() {
1965
+ const now = Date.now() / 1e3;
1966
+ const publicKeys = await this.listPublicKeys();
1967
+ this.keyStore = jose.JWKS.asKeyStore({
1968
+ keys: publicKeys.keys.map((key) => key)
1969
+ });
1970
+ this.keyStoreUpdated = now;
1802
1971
  }
1803
1972
  }
1804
1973
 
1805
- const atlassianDefaultAuthHandler = async ({
1806
- fullProfile,
1807
- params
1808
- }) => ({
1809
- profile: makeProfileInfo(fullProfile, params.id_token)
1810
- });
1811
- class AtlassianAuthProvider {
1974
+ const MS_IN_S = 1e3;
1975
+ class TokenFactory {
1812
1976
  constructor(options) {
1813
- this.catalogIdentityClient = options.catalogIdentityClient;
1977
+ this.issuer = options.issuer;
1814
1978
  this.logger = options.logger;
1815
- this.tokenIssuer = options.tokenIssuer;
1816
- this.authHandler = options.authHandler;
1817
- this.signInResolver = options.signInResolver;
1818
- this._strategy = new AtlassianStrategy({
1819
- clientID: options.clientId,
1820
- clientSecret: options.clientSecret,
1821
- callbackURL: options.callbackUrl,
1822
- scope: options.scopes
1823
- }, (accessToken, refreshToken, params, fullProfile, done) => {
1824
- done(void 0, {
1825
- fullProfile,
1826
- accessToken,
1827
- refreshToken,
1828
- params
1829
- });
1830
- });
1979
+ this.keyStore = options.keyStore;
1980
+ this.keyDurationSeconds = options.keyDurationSeconds;
1831
1981
  }
1832
- async start(req) {
1833
- return await executeRedirectStrategy(req, this._strategy, {
1834
- state: encodeState(req.state)
1982
+ async issueToken(params) {
1983
+ const key = await this.getKey();
1984
+ const iss = this.issuer;
1985
+ const sub = params.claims.sub;
1986
+ const ent = params.claims.ent;
1987
+ const aud = "backstage";
1988
+ const iat = Math.floor(Date.now() / MS_IN_S);
1989
+ const exp = iat + this.keyDurationSeconds;
1990
+ this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
1991
+ return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
1992
+ alg: key.alg,
1993
+ kid: key.kid
1835
1994
  });
1836
1995
  }
1837
- async handler(req) {
1838
- var _a;
1839
- const { result } = await executeFrameHandlerStrategy(req, this._strategy);
1840
- return {
1841
- response: await this.handleResult(result),
1842
- refreshToken: (_a = result.refreshToken) != null ? _a : ""
1843
- };
1844
- }
1845
- async handleResult(result) {
1846
- const { profile } = await this.authHandler(result);
1847
- const response = {
1848
- providerInfo: {
1849
- idToken: result.params.id_token,
1850
- accessToken: result.accessToken,
1851
- refreshToken: result.refreshToken,
1852
- scope: result.params.scope,
1853
- expiresInSeconds: result.params.expires_in
1854
- },
1855
- profile
1856
- };
1857
- if (this.signInResolver) {
1858
- response.backstageIdentity = await this.signInResolver({
1859
- result,
1860
- profile
1861
- }, {
1862
- tokenIssuer: this.tokenIssuer,
1863
- catalogIdentityClient: this.catalogIdentityClient,
1864
- logger: this.logger
1996
+ async listPublicKeys() {
1997
+ const { items: keys } = await this.keyStore.listKeys();
1998
+ const validKeys = [];
1999
+ const expiredKeys = [];
2000
+ for (const key of keys) {
2001
+ const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
2002
+ seconds: 3 * this.keyDurationSeconds
1865
2003
  });
2004
+ if (expireAt < luxon.DateTime.local()) {
2005
+ expiredKeys.push(key);
2006
+ } else {
2007
+ validKeys.push(key);
2008
+ }
1866
2009
  }
1867
- return response;
2010
+ if (expiredKeys.length > 0) {
2011
+ const kids = expiredKeys.map(({ key }) => key.kid);
2012
+ this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
2013
+ this.keyStore.removeKeys(kids).catch((error) => {
2014
+ this.logger.error(`Failed to remove expired keys, ${error}`);
2015
+ });
2016
+ }
2017
+ return { keys: validKeys.map(({ key }) => key) };
1868
2018
  }
1869
- async refresh(req) {
1870
- const {
1871
- accessToken,
1872
- params,
1873
- refreshToken: newRefreshToken
1874
- } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1875
- const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1876
- return this.handleResult({
1877
- fullProfile,
1878
- params,
1879
- accessToken,
1880
- refreshToken: newRefreshToken
1881
- });
2019
+ async getKey() {
2020
+ if (this.privateKeyPromise) {
2021
+ if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
2022
+ return this.privateKeyPromise;
2023
+ }
2024
+ this.logger.info(`Signing key has expired, generating new key`);
2025
+ delete this.privateKeyPromise;
2026
+ }
2027
+ this.keyExpiry = luxon.DateTime.utc().plus({
2028
+ seconds: this.keyDurationSeconds
2029
+ }).toJSDate();
2030
+ const promise = (async () => {
2031
+ const key = await jose.JWK.generate("EC", "P-256", {
2032
+ use: "sig",
2033
+ kid: uuid.v4(),
2034
+ alg: "ES256"
2035
+ });
2036
+ this.logger.info(`Created new signing key ${key.kid}`);
2037
+ await this.keyStore.addKey(key.toJWK(false));
2038
+ return key;
2039
+ })();
2040
+ this.privateKeyPromise = promise;
2041
+ try {
2042
+ await promise;
2043
+ } catch (error) {
2044
+ this.logger.error(`Failed to generate new signing key, ${error}`);
2045
+ delete this.keyExpiry;
2046
+ delete this.privateKeyPromise;
2047
+ }
2048
+ return promise;
1882
2049
  }
1883
2050
  }
1884
- const createAtlassianProvider = (options) => {
1885
- return ({
1886
- providerId,
1887
- globalConfig,
1888
- config,
1889
- tokenIssuer,
1890
- catalogApi,
1891
- logger
1892
- }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1893
- var _a, _b;
1894
- const clientId = envConfig.getString("clientId");
1895
- const clientSecret = envConfig.getString("clientSecret");
1896
- const scopes = envConfig.getString("scopes");
1897
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1898
- const catalogIdentityClient = new CatalogIdentityClient({
1899
- catalogApi,
1900
- tokenIssuer
2051
+
2052
+ const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
2053
+ const TABLE = "signing_keys";
2054
+ const parseDate = (date) => {
2055
+ const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
2056
+ if (!parsedDate.isValid) {
2057
+ throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
2058
+ }
2059
+ return parsedDate.toJSDate();
2060
+ };
2061
+ class DatabaseKeyStore {
2062
+ static async create(options) {
2063
+ const { database } = options;
2064
+ await database.migrate.latest({
2065
+ directory: migrationsDir
1901
2066
  });
1902
- const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : atlassianDefaultAuthHandler;
1903
- const provider = new AtlassianAuthProvider({
1904
- clientId,
1905
- clientSecret,
1906
- scopes,
1907
- callbackUrl,
1908
- authHandler,
1909
- signInResolver: (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver,
1910
- catalogIdentityClient,
1911
- logger,
1912
- tokenIssuer
2067
+ return new DatabaseKeyStore(options);
2068
+ }
2069
+ constructor(options) {
2070
+ this.database = options.database;
2071
+ }
2072
+ async addKey(key) {
2073
+ await this.database(TABLE).insert({
2074
+ kid: key.kid,
2075
+ key: JSON.stringify(key)
1913
2076
  });
1914
- return OAuthAdapter.fromConfig(globalConfig, provider, {
1915
- disableRefresh: true,
1916
- providerId,
1917
- tokenIssuer
2077
+ }
2078
+ async listKeys() {
2079
+ const rows = await this.database(TABLE).select();
2080
+ return {
2081
+ items: rows.map((row) => ({
2082
+ key: JSON.parse(row.key),
2083
+ createdAt: parseDate(row.created_at)
2084
+ }))
2085
+ };
2086
+ }
2087
+ async removeKeys(kids) {
2088
+ await this.database(TABLE).delete().whereIn("kid", kids);
2089
+ }
2090
+ }
2091
+
2092
+ class MemoryKeyStore {
2093
+ constructor() {
2094
+ this.keys = /* @__PURE__ */ new Map();
2095
+ }
2096
+ async addKey(key) {
2097
+ this.keys.set(key.kid, {
2098
+ createdAt: luxon.DateTime.utc().toJSDate(),
2099
+ key: JSON.stringify(key)
1918
2100
  });
1919
- });
1920
- };
2101
+ }
2102
+ async removeKeys(kids) {
2103
+ for (const kid of kids) {
2104
+ this.keys.delete(kid);
2105
+ }
2106
+ }
2107
+ async listKeys() {
2108
+ return {
2109
+ items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
2110
+ createdAt,
2111
+ key: JSON.parse(keyStr)
2112
+ }))
2113
+ };
2114
+ }
2115
+ }
1921
2116
 
1922
- const ALB_JWT_HEADER = "x-amzn-oidc-data";
1923
- const ALB_ACCESSTOKEN_HEADER = "x-amzn-oidc-accesstoken";
1924
- const getJWTHeaders = (input) => {
1925
- const encoded = input.split(".")[0];
1926
- return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
1927
- };
1928
- class AwsAlbAuthProvider {
2117
+ const DEFAULT_TIMEOUT_MS = 1e4;
2118
+ const DEFAULT_DOCUMENT_PATH = "sessions";
2119
+ class FirestoreKeyStore {
2120
+ constructor(database, path, timeout) {
2121
+ this.database = database;
2122
+ this.path = path;
2123
+ this.timeout = timeout;
2124
+ }
2125
+ static async create(settings) {
2126
+ const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
2127
+ const database = new firestore.Firestore(firestoreSettings);
2128
+ return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
2129
+ }
2130
+ static async verifyConnection(keyStore, logger) {
2131
+ try {
2132
+ await keyStore.verify();
2133
+ } catch (error) {
2134
+ if (process.env.NODE_ENV !== "development") {
2135
+ throw new Error(`Failed to connect to database: ${error.message}`);
2136
+ }
2137
+ logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
2138
+ }
2139
+ }
2140
+ async addKey(key) {
2141
+ await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
2142
+ kid: key.kid,
2143
+ key: JSON.stringify(key)
2144
+ }));
2145
+ }
2146
+ async listKeys() {
2147
+ const keys = await this.withTimeout(this.database.collection(this.path).get());
2148
+ return {
2149
+ items: keys.docs.map((key) => ({
2150
+ key: key.data(),
2151
+ createdAt: key.createTime.toDate()
2152
+ }))
2153
+ };
2154
+ }
2155
+ async removeKeys(kids) {
2156
+ for (const kid of kids) {
2157
+ await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
2158
+ }
2159
+ }
2160
+ async withTimeout(operation) {
2161
+ const timer = new Promise((_, reject) => setTimeout(() => {
2162
+ reject(new Error(`Operation timed out after ${this.timeout}ms`));
2163
+ }, this.timeout));
2164
+ return Promise.race([operation, timer]);
2165
+ }
2166
+ async verify() {
2167
+ await this.withTimeout(this.database.collection(this.path).limit(1).get());
2168
+ }
2169
+ }
2170
+
2171
+ class KeyStores {
2172
+ static async fromConfig(config, options) {
2173
+ var _a;
2174
+ const { logger, database } = options != null ? options : {};
2175
+ const ks = config.getOptionalConfig("auth.keyStore");
2176
+ const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
2177
+ logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
2178
+ if (provider === "database") {
2179
+ if (!database) {
2180
+ throw new Error("This KeyStore provider requires a database");
2181
+ }
2182
+ return await DatabaseKeyStore.create({
2183
+ database: await database.getClient()
2184
+ });
2185
+ }
2186
+ if (provider === "memory") {
2187
+ return new MemoryKeyStore();
2188
+ }
2189
+ if (provider === "firestore") {
2190
+ const settings = ks == null ? void 0 : ks.getConfig(provider);
2191
+ const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
2192
+ projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
2193
+ keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
2194
+ host: settings == null ? void 0 : settings.getOptionalString("host"),
2195
+ port: settings == null ? void 0 : settings.getOptionalNumber("port"),
2196
+ ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
2197
+ path: settings == null ? void 0 : settings.getOptionalString("path"),
2198
+ timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
2199
+ }, (value) => value !== void 0));
2200
+ await FirestoreKeyStore.verifyConnection(keyStore, logger);
2201
+ return keyStore;
2202
+ }
2203
+ throw new Error(`Unknown KeyStore provider: ${provider}`);
2204
+ }
2205
+ }
2206
+
2207
+ const OAUTH2_PROXY_JWT_HEADER = "X-OAUTH2-PROXY-ID-TOKEN";
2208
+ class Oauth2ProxyAuthProvider {
1929
2209
  constructor(options) {
1930
- this.region = options.region;
1931
- this.issuer = options.issuer;
1932
- this.authHandler = options.authHandler;
1933
- this.signInResolver = options.signInResolver;
1934
- this.tokenIssuer = options.tokenIssuer;
1935
2210
  this.catalogIdentityClient = options.catalogIdentityClient;
1936
2211
  this.logger = options.logger;
1937
- this.keyCache = new NodeCache__default["default"]({ stdTTL: 3600 });
2212
+ this.tokenIssuer = options.tokenIssuer;
2213
+ this.signInResolver = options.signInResolver;
2214
+ this.authHandler = options.authHandler;
1938
2215
  }
1939
2216
  frameHandler() {
1940
2217
  return Promise.resolve(void 0);
1941
2218
  }
1942
2219
  async refresh(req, res) {
1943
2220
  try {
1944
- const result = await this.getResult(req);
2221
+ const result = this.getResult(req);
1945
2222
  const response = await this.handleResult(result);
1946
2223
  res.json(response);
1947
2224
  } catch (e) {
1948
- this.logger.error("Exception occurred during AWS ALB token refresh", e);
2225
+ this.logger.error(`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`, e);
1949
2226
  res.status(401);
1950
2227
  res.end();
1951
2228
  }
@@ -1953,159 +2230,159 @@ class AwsAlbAuthProvider {
1953
2230
  start() {
1954
2231
  return Promise.resolve(void 0);
1955
2232
  }
1956
- async getResult(req) {
1957
- const jwt = req.header(ALB_JWT_HEADER);
1958
- const accessToken = req.header(ALB_ACCESSTOKEN_HEADER);
1959
- if (jwt === void 0) {
1960
- throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_JWT_HEADER}`);
1961
- }
1962
- if (accessToken === void 0) {
1963
- throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_ACCESSTOKEN_HEADER}`);
1964
- }
1965
- try {
1966
- const headers = getJWTHeaders(jwt);
1967
- const key = await this.getKey(headers.kid);
1968
- const claims = jose.JWT.verify(jwt, key);
1969
- if (this.issuer && claims.iss !== this.issuer) {
1970
- throw new errors.AuthenticationError("Issuer mismatch on JWT token");
1971
- }
1972
- const fullProfile = {
1973
- provider: "unknown",
1974
- id: claims.sub,
1975
- displayName: claims.name,
1976
- username: claims.email.split("@")[0].toLowerCase(),
1977
- name: {
1978
- familyName: claims.family_name,
1979
- givenName: claims.given_name
1980
- },
1981
- emails: [{ value: claims.email.toLowerCase() }],
1982
- photos: [{ value: claims.picture }]
1983
- };
1984
- return {
1985
- fullProfile,
1986
- expiresInSeconds: claims.exp,
1987
- accessToken
1988
- };
1989
- } catch (e) {
1990
- throw new Error(`Exception occurred during JWT processing: ${e}`);
1991
- }
1992
- }
1993
2233
  async handleResult(result) {
1994
- const { profile } = await this.authHandler(result);
1995
- const backstageIdentity = await this.signInResolver({
2234
+ const ctx = {
2235
+ logger: this.logger,
2236
+ tokenIssuer: this.tokenIssuer,
2237
+ catalogIdentityClient: this.catalogIdentityClient
2238
+ };
2239
+ const { profile } = await this.authHandler(result, ctx);
2240
+ const backstageSignInResult = await this.signInResolver({
1996
2241
  result,
1997
2242
  profile
1998
- }, {
1999
- tokenIssuer: this.tokenIssuer,
2000
- catalogIdentityClient: this.catalogIdentityClient,
2001
- logger: this.logger
2002
- });
2243
+ }, ctx);
2003
2244
  return {
2004
2245
  providerInfo: {
2005
- accessToken: result.accessToken,
2006
- expiresInSeconds: result.expiresInSeconds
2246
+ accessToken: result.accessToken
2007
2247
  },
2008
- backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
2248
+ backstageIdentity: prepareBackstageIdentityResponse(backstageSignInResult),
2009
2249
  profile
2010
2250
  };
2011
2251
  }
2012
- async getKey(keyId) {
2013
- const optionalCacheKey = this.keyCache.get(keyId);
2014
- if (optionalCacheKey) {
2015
- return crypto__namespace.createPublicKey(optionalCacheKey);
2252
+ getResult(req) {
2253
+ const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER);
2254
+ const jwt = IdentityClient.getBearerToken(authHeader);
2255
+ if (!jwt) {
2256
+ throw new errors.AuthenticationError(`Missing or in incorrect format - Oauth2Proxy OIDC header: ${OAUTH2_PROXY_JWT_HEADER}`);
2016
2257
  }
2017
- const keyText = await fetch__default["default"](`https://public-keys.auth.elb.${this.region}.amazonaws.com/${keyId}`).then((response) => response.text());
2018
- const keyValue = crypto__namespace.createPublicKey(keyText);
2019
- this.keyCache.set(keyId, keyValue.export({ format: "pem", type: "spki" }));
2020
- return keyValue;
2258
+ const decodedJWT = jose.JWT.decode(jwt);
2259
+ return {
2260
+ fullProfile: decodedJWT,
2261
+ accessToken: jwt
2262
+ };
2021
2263
  }
2022
2264
  }
2023
- const createAwsAlbProvider = (options) => {
2024
- return ({ config, tokenIssuer, catalogApi, logger }) => {
2025
- const region = config.getString("region");
2026
- const issuer = config.getOptionalString("iss");
2027
- if ((options == null ? void 0 : options.signIn.resolver) === void 0) {
2028
- throw new Error("SignInResolver is required to use this authentication provider");
2029
- }
2030
- const catalogIdentityClient = new CatalogIdentityClient({
2031
- catalogApi,
2032
- tokenIssuer
2033
- });
2034
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
2035
- profile: makeProfileInfo(fullProfile)
2036
- });
2037
- const signInResolver = options == null ? void 0 : options.signIn.resolver;
2038
- return new AwsAlbAuthProvider({
2039
- region,
2040
- issuer,
2041
- signInResolver,
2042
- authHandler,
2043
- tokenIssuer,
2044
- catalogIdentityClient,
2045
- logger
2046
- });
2047
- };
2265
+ const createOauth2ProxyProvider = (options) => ({ catalogApi, logger, tokenIssuer }) => {
2266
+ const signInResolver = options.signIn.resolver;
2267
+ const authHandler = options.authHandler;
2268
+ const catalogIdentityClient = new CatalogIdentityClient({
2269
+ catalogApi,
2270
+ tokenIssuer
2271
+ });
2272
+ return new Oauth2ProxyAuthProvider({
2273
+ logger,
2274
+ signInResolver,
2275
+ authHandler,
2276
+ tokenIssuer,
2277
+ catalogIdentityClient
2278
+ });
2048
2279
  };
2049
2280
 
2050
- class SamlAuthProvider {
2281
+ class OidcAuthProvider {
2051
2282
  constructor(options) {
2052
- this.appUrl = options.appUrl;
2283
+ this.implementation = this.setupStrategy(options);
2284
+ this.scope = options.scope;
2285
+ this.prompt = options.prompt;
2053
2286
  this.signInResolver = options.signInResolver;
2054
2287
  this.authHandler = options.authHandler;
2055
2288
  this.tokenIssuer = options.tokenIssuer;
2056
2289
  this.catalogIdentityClient = options.catalogIdentityClient;
2057
2290
  this.logger = options.logger;
2058
- this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
2059
- done(void 0, { fullProfile });
2060
- });
2061
2291
  }
2062
- async start(req, res) {
2063
- const { url } = await executeRedirectStrategy(req, this.strategy, {});
2064
- res.redirect(url);
2292
+ async start(req) {
2293
+ const { strategy } = await this.implementation;
2294
+ const options = {
2295
+ scope: req.scope || this.scope || "openid profile email",
2296
+ state: encodeState(req.state)
2297
+ };
2298
+ const prompt = this.prompt || "none";
2299
+ if (prompt !== "auto") {
2300
+ options.prompt = prompt;
2301
+ }
2302
+ return await executeRedirectStrategy(req, strategy, options);
2065
2303
  }
2066
- async frameHandler(req, res) {
2067
- try {
2068
- const { result } = await executeFrameHandlerStrategy(req, this.strategy);
2069
- const { profile } = await this.authHandler(result);
2070
- const response = {
2071
- profile,
2072
- providerInfo: {}
2073
- };
2074
- if (this.signInResolver) {
2075
- const signInResponse = await this.signInResolver({
2076
- result,
2077
- profile
2078
- }, {
2079
- tokenIssuer: this.tokenIssuer,
2080
- catalogIdentityClient: this.catalogIdentityClient,
2081
- logger: this.logger
2082
- });
2083
- response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
2304
+ async handler(req) {
2305
+ const { strategy } = await this.implementation;
2306
+ const { result, privateInfo } = await executeFrameHandlerStrategy(req, strategy);
2307
+ return {
2308
+ response: await this.handleResult(result),
2309
+ refreshToken: privateInfo.refreshToken
2310
+ };
2311
+ }
2312
+ async refresh(req) {
2313
+ const { client } = await this.implementation;
2314
+ const tokenset = await client.refresh(req.refreshToken);
2315
+ if (!tokenset.access_token) {
2316
+ throw new Error("Refresh failed");
2317
+ }
2318
+ const userinfo = await client.userinfo(tokenset.access_token);
2319
+ return {
2320
+ response: await this.handleResult({ tokenset, userinfo }),
2321
+ refreshToken: tokenset.refresh_token
2322
+ };
2323
+ }
2324
+ async setupStrategy(options) {
2325
+ const issuer = await openidClient.Issuer.discover(options.metadataUrl);
2326
+ const client = new issuer.Client({
2327
+ access_type: "offline",
2328
+ client_id: options.clientId,
2329
+ client_secret: options.clientSecret,
2330
+ redirect_uris: [options.callbackUrl],
2331
+ response_types: ["code"],
2332
+ id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
2333
+ scope: options.scope || ""
2334
+ });
2335
+ const strategy = new openidClient.Strategy({
2336
+ client,
2337
+ passReqToCallback: false
2338
+ }, (tokenset, userinfo, done) => {
2339
+ if (typeof done !== "function") {
2340
+ throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
2084
2341
  }
2085
- return postMessageResponse(res, this.appUrl, {
2086
- type: "authorization_response",
2087
- response
2088
- });
2089
- } catch (error) {
2090
- const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
2091
- return postMessageResponse(res, this.appUrl, {
2092
- type: "authorization_response",
2093
- error: { name, message }
2342
+ done(void 0, { tokenset, userinfo }, {
2343
+ refreshToken: tokenset.refresh_token
2094
2344
  });
2095
- }
2345
+ });
2346
+ strategy.error = console.error;
2347
+ return { strategy, client };
2096
2348
  }
2097
- async logout(_req, res) {
2098
- res.end();
2349
+ async handleResult(result) {
2350
+ const context = {
2351
+ logger: this.logger,
2352
+ catalogIdentityClient: this.catalogIdentityClient,
2353
+ tokenIssuer: this.tokenIssuer
2354
+ };
2355
+ const { profile } = await this.authHandler(result, context);
2356
+ const response = {
2357
+ providerInfo: {
2358
+ idToken: result.tokenset.id_token,
2359
+ accessToken: result.tokenset.access_token,
2360
+ scope: result.tokenset.scope,
2361
+ expiresInSeconds: result.tokenset.expires_in
2362
+ },
2363
+ profile
2364
+ };
2365
+ if (this.signInResolver) {
2366
+ response.backstageIdentity = await this.signInResolver({
2367
+ result,
2368
+ profile
2369
+ }, context);
2370
+ }
2371
+ return response;
2099
2372
  }
2100
2373
  }
2101
- const samlDefaultSignInResolver = async (info, ctx) => {
2102
- const id = info.result.fullProfile.nameID;
2374
+ const oAuth2DefaultSignInResolver = async (info, ctx) => {
2375
+ const { profile } = info;
2376
+ if (!profile.email) {
2377
+ throw new Error("Profile contained no email");
2378
+ }
2379
+ const userId = profile.email.split("@")[0];
2103
2380
  const token = await ctx.tokenIssuer.issueToken({
2104
- claims: { sub: id }
2381
+ claims: { sub: userId, ent: [`user:default/${userId}`] }
2105
2382
  });
2106
- return { id, token };
2383
+ return { id: userId, token };
2107
2384
  };
2108
- const createSamlProvider = (options) => {
2385
+ const createOidcProvider = (options) => {
2109
2386
  return ({
2110
2387
  providerId,
2111
2388
  globalConfig,
@@ -2113,75 +2390,83 @@ const createSamlProvider = (options) => {
2113
2390
  tokenIssuer,
2114
2391
  catalogApi,
2115
2392
  logger
2116
- }) => {
2393
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2117
2394
  var _a, _b;
2395
+ const clientId = envConfig.getString("clientId");
2396
+ const clientSecret = envConfig.getString("clientSecret");
2397
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2398
+ const metadataUrl = envConfig.getString("metadataUrl");
2399
+ const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
2400
+ const scope = envConfig.getOptionalString("scope");
2401
+ const prompt = envConfig.getOptionalString("prompt");
2118
2402
  const catalogIdentityClient = new CatalogIdentityClient({
2119
2403
  catalogApi,
2120
2404
  tokenIssuer
2121
2405
  });
2122
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
2406
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ userinfo }) => ({
2123
2407
  profile: {
2124
- email: fullProfile.email,
2125
- displayName: fullProfile.displayName
2408
+ displayName: userinfo.name,
2409
+ email: userinfo.email,
2410
+ picture: userinfo.picture
2126
2411
  }
2127
2412
  });
2128
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
2413
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oAuth2DefaultSignInResolver;
2129
2414
  const signInResolver = (info) => signInResolverFn(info, {
2130
2415
  catalogIdentityClient,
2131
2416
  tokenIssuer,
2132
2417
  logger
2133
2418
  });
2134
- return new SamlAuthProvider({
2135
- callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
2136
- entryPoint: config.getString("entryPoint"),
2137
- logoutUrl: config.getOptionalString("logoutUrl"),
2138
- audience: config.getOptionalString("audience"),
2139
- issuer: config.getString("issuer"),
2140
- cert: config.getString("cert"),
2141
- privateKey: config.getOptionalString("privateKey"),
2142
- authnContext: config.getOptionalStringArray("authnContext"),
2143
- identifierFormat: config.getOptionalString("identifierFormat"),
2144
- decryptionPvk: config.getOptionalString("decryptionPvk"),
2145
- signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
2146
- digestAlgorithm: config.getOptionalString("digestAlgorithm"),
2147
- acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
2148
- tokenIssuer,
2149
- appUrl: globalConfig.appUrl,
2150
- authHandler,
2419
+ const provider = new OidcAuthProvider({
2420
+ clientId,
2421
+ clientSecret,
2422
+ callbackUrl,
2423
+ tokenSignedResponseAlg,
2424
+ metadataUrl,
2425
+ scope,
2426
+ prompt,
2151
2427
  signInResolver,
2428
+ authHandler,
2152
2429
  logger,
2430
+ tokenIssuer,
2153
2431
  catalogIdentityClient
2154
2432
  });
2155
- };
2433
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
2434
+ disableRefresh: false,
2435
+ providerId,
2436
+ tokenIssuer
2437
+ });
2438
+ });
2156
2439
  };
2157
2440
 
2158
- class Auth0Strategy extends OAuth2Strategy__default["default"] {
2159
- constructor(options, verify) {
2160
- const optionsWithURLs = {
2161
- ...options,
2162
- authorizationURL: `https://${options.domain}/authorize`,
2163
- tokenURL: `https://${options.domain}/oauth/token`,
2164
- userInfoURL: `https://${options.domain}/userinfo`,
2165
- apiUrl: `https://${options.domain}/api`
2166
- };
2167
- super(optionsWithURLs, verify);
2168
- }
2169
- }
2170
-
2171
- class Auth0AuthProvider {
2441
+ class OktaAuthProvider {
2172
2442
  constructor(options) {
2173
- this._strategy = new Auth0Strategy({
2443
+ this._store = {
2444
+ store(_req, cb) {
2445
+ cb(null, null);
2446
+ },
2447
+ verify(_req, _state, cb) {
2448
+ cb(null, true);
2449
+ }
2450
+ };
2451
+ this._signInResolver = options.signInResolver;
2452
+ this._authHandler = options.authHandler;
2453
+ this._tokenIssuer = options.tokenIssuer;
2454
+ this._catalogIdentityClient = options.catalogIdentityClient;
2455
+ this._logger = options.logger;
2456
+ this._strategy = new passportOktaOauth.Strategy({
2174
2457
  clientID: options.clientId,
2175
2458
  clientSecret: options.clientSecret,
2176
2459
  callbackURL: options.callbackUrl,
2177
- domain: options.domain,
2178
- passReqToCallback: false
2460
+ audience: options.audience,
2461
+ passReqToCallback: false,
2462
+ store: this._store,
2463
+ response_type: "code"
2179
2464
  }, (accessToken, refreshToken, params, fullProfile, done) => {
2180
2465
  done(void 0, {
2181
- fullProfile,
2182
2466
  accessToken,
2183
2467
  refreshToken,
2184
- params
2468
+ params,
2469
+ fullProfile
2185
2470
  }, {
2186
2471
  refreshToken
2187
2472
  });
@@ -2197,57 +2482,116 @@ class Auth0AuthProvider {
2197
2482
  }
2198
2483
  async handler(req) {
2199
2484
  const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
2200
- const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
2201
2485
  return {
2202
- response: await this.populateIdentity({
2203
- profile,
2204
- providerInfo: {
2205
- idToken: result.params.id_token,
2206
- accessToken: result.accessToken,
2207
- scope: result.params.scope,
2208
- expiresInSeconds: result.params.expires_in
2209
- }
2210
- }),
2486
+ response: await this.handleResult(result),
2211
2487
  refreshToken: privateInfo.refreshToken
2212
2488
  };
2213
2489
  }
2214
2490
  async refresh(req) {
2215
- const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
2491
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
2216
2492
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
2217
- const profile = makeProfileInfo(fullProfile, params.id_token);
2218
- return this.populateIdentity({
2493
+ return {
2494
+ response: await this.handleResult({
2495
+ fullProfile,
2496
+ params,
2497
+ accessToken
2498
+ }),
2499
+ refreshToken
2500
+ };
2501
+ }
2502
+ async handleResult(result) {
2503
+ const context = {
2504
+ logger: this._logger,
2505
+ catalogIdentityClient: this._catalogIdentityClient,
2506
+ tokenIssuer: this._tokenIssuer
2507
+ };
2508
+ const { profile } = await this._authHandler(result, context);
2509
+ const response = {
2219
2510
  providerInfo: {
2220
- accessToken,
2221
- idToken: params.id_token,
2222
- expiresInSeconds: params.expires_in,
2223
- scope: params.scope
2511
+ idToken: result.params.id_token,
2512
+ accessToken: result.accessToken,
2513
+ scope: result.params.scope,
2514
+ expiresInSeconds: result.params.expires_in
2224
2515
  },
2225
2516
  profile
2226
- });
2227
- }
2228
- async populateIdentity(response) {
2229
- const { profile } = response;
2230
- if (!profile.email) {
2231
- throw new Error("Profile does not contain an email");
2517
+ };
2518
+ if (this._signInResolver) {
2519
+ response.backstageIdentity = await this._signInResolver({
2520
+ result,
2521
+ profile
2522
+ }, context);
2232
2523
  }
2233
- const id = profile.email.split("@")[0];
2234
- return { ...response, backstageIdentity: { id, token: "" } };
2524
+ return response;
2235
2525
  }
2236
2526
  }
2237
- const createAuth0Provider = (_options) => {
2238
- return ({ providerId, globalConfig, config, tokenIssuer }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2527
+ const oktaEmailSignInResolver = async (info, ctx) => {
2528
+ const { profile } = info;
2529
+ if (!profile.email) {
2530
+ throw new Error("Okta profile contained no email");
2531
+ }
2532
+ const entity = await ctx.catalogIdentityClient.findUser({
2533
+ annotations: {
2534
+ "okta.com/email": profile.email
2535
+ }
2536
+ });
2537
+ const claims = getEntityClaims(entity);
2538
+ const token = await ctx.tokenIssuer.issueToken({ claims });
2539
+ return { id: entity.metadata.name, entity, token };
2540
+ };
2541
+ const oktaDefaultSignInResolver = async (info, ctx) => {
2542
+ const { profile } = info;
2543
+ if (!profile.email) {
2544
+ throw new Error("Okta profile contained no email");
2545
+ }
2546
+ const userId = profile.email.split("@")[0];
2547
+ const token = await ctx.tokenIssuer.issueToken({
2548
+ claims: { sub: userId, ent: [`user:default/${userId}`] }
2549
+ });
2550
+ return { id: userId, token };
2551
+ };
2552
+ const createOktaProvider = (_options) => {
2553
+ return ({
2554
+ providerId,
2555
+ globalConfig,
2556
+ config,
2557
+ tokenIssuer,
2558
+ catalogApi,
2559
+ logger
2560
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2561
+ var _a, _b;
2239
2562
  const clientId = envConfig.getString("clientId");
2240
2563
  const clientSecret = envConfig.getString("clientSecret");
2241
- const domain = envConfig.getString("domain");
2564
+ const audience = envConfig.getString("audience");
2242
2565
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2243
- const provider = new Auth0AuthProvider({
2566
+ if (!audience.startsWith("https://")) {
2567
+ throw new Error("URL for 'audience' must start with 'https://'.");
2568
+ }
2569
+ const catalogIdentityClient = new CatalogIdentityClient({
2570
+ catalogApi,
2571
+ tokenIssuer
2572
+ });
2573
+ const authHandler = (_options == null ? void 0 : _options.authHandler) ? _options.authHandler : async ({ fullProfile, params }) => ({
2574
+ profile: makeProfileInfo(fullProfile, params.id_token)
2575
+ });
2576
+ const signInResolverFn = (_b = (_a = _options == null ? void 0 : _options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oktaDefaultSignInResolver;
2577
+ const signInResolver = (info) => signInResolverFn(info, {
2578
+ catalogIdentityClient,
2579
+ tokenIssuer,
2580
+ logger
2581
+ });
2582
+ const provider = new OktaAuthProvider({
2583
+ audience,
2244
2584
  clientId,
2245
2585
  clientSecret,
2246
2586
  callbackUrl,
2247
- domain
2587
+ authHandler,
2588
+ signInResolver,
2589
+ tokenIssuer,
2590
+ catalogIdentityClient,
2591
+ logger
2248
2592
  });
2249
2593
  return OAuthAdapter.fromConfig(globalConfig, provider, {
2250
- disableRefresh: true,
2594
+ disableRefresh: false,
2251
2595
  providerId,
2252
2596
  tokenIssuer
2253
2597
  });
@@ -2256,6 +2600,11 @@ const createAuth0Provider = (_options) => {
2256
2600
 
2257
2601
  class OneLoginProvider {
2258
2602
  constructor(options) {
2603
+ this.signInResolver = options.signInResolver;
2604
+ this.authHandler = options.authHandler;
2605
+ this.tokenIssuer = options.tokenIssuer;
2606
+ this.catalogIdentityClient = options.catalogIdentityClient;
2607
+ this.logger = options.logger;
2259
2608
  this._strategy = new passportOneloginOauth.Strategy({
2260
2609
  issuer: options.issuer,
2261
2610
  clientID: options.clientId,
@@ -2283,54 +2632,88 @@ class OneLoginProvider {
2283
2632
  }
2284
2633
  async handler(req) {
2285
2634
  const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
2286
- const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
2287
2635
  return {
2288
- response: await this.populateIdentity({
2289
- profile,
2290
- providerInfo: {
2291
- idToken: result.params.id_token,
2292
- accessToken: result.accessToken,
2293
- scope: result.params.scope,
2294
- expiresInSeconds: result.params.expires_in
2295
- }
2296
- }),
2636
+ response: await this.handleResult(result),
2297
2637
  refreshToken: privateInfo.refreshToken
2298
2638
  };
2299
2639
  }
2300
2640
  async refresh(req) {
2301
- const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
2641
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
2302
2642
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
2303
- const profile = makeProfileInfo(fullProfile, params.id_token);
2304
- return this.populateIdentity({
2643
+ return {
2644
+ response: await this.handleResult({
2645
+ fullProfile,
2646
+ params,
2647
+ accessToken
2648
+ }),
2649
+ refreshToken
2650
+ };
2651
+ }
2652
+ async handleResult(result) {
2653
+ const context = {
2654
+ logger: this.logger,
2655
+ catalogIdentityClient: this.catalogIdentityClient,
2656
+ tokenIssuer: this.tokenIssuer
2657
+ };
2658
+ const { profile } = await this.authHandler(result, context);
2659
+ const response = {
2305
2660
  providerInfo: {
2306
- accessToken,
2307
- idToken: params.id_token,
2308
- expiresInSeconds: params.expires_in,
2309
- scope: params.scope
2661
+ idToken: result.params.id_token,
2662
+ accessToken: result.accessToken,
2663
+ scope: result.params.scope,
2664
+ expiresInSeconds: result.params.expires_in
2310
2665
  },
2311
2666
  profile
2312
- });
2313
- }
2314
- async populateIdentity(response) {
2315
- const { profile } = response;
2316
- if (!profile.email) {
2317
- throw new Error("OIDC profile contained no email");
2667
+ };
2668
+ if (this.signInResolver) {
2669
+ response.backstageIdentity = await this.signInResolver({
2670
+ result,
2671
+ profile
2672
+ }, context);
2318
2673
  }
2319
- const id = profile.email.split("@")[0];
2320
- return { ...response, backstageIdentity: { id, token: "" } };
2674
+ return response;
2321
2675
  }
2322
2676
  }
2323
- const createOneLoginProvider = (_options) => {
2324
- return ({ providerId, globalConfig, config, tokenIssuer }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2677
+ const defaultSignInResolver = async (info) => {
2678
+ const { profile } = info;
2679
+ if (!profile.email) {
2680
+ throw new Error("OIDC profile contained no email");
2681
+ }
2682
+ const id = profile.email.split("@")[0];
2683
+ return { id, token: "" };
2684
+ };
2685
+ const createOneLoginProvider = (options) => {
2686
+ return ({
2687
+ providerId,
2688
+ globalConfig,
2689
+ config,
2690
+ tokenIssuer,
2691
+ catalogApi,
2692
+ logger
2693
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2694
+ var _a, _b;
2325
2695
  const clientId = envConfig.getString("clientId");
2326
2696
  const clientSecret = envConfig.getString("clientSecret");
2327
2697
  const issuer = envConfig.getString("issuer");
2328
2698
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2699
+ const catalogIdentityClient = new CatalogIdentityClient({
2700
+ catalogApi,
2701
+ tokenIssuer
2702
+ });
2703
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
2704
+ profile: makeProfileInfo(fullProfile, params.id_token)
2705
+ });
2706
+ const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
2329
2707
  const provider = new OneLoginProvider({
2330
2708
  clientId,
2331
2709
  clientSecret,
2332
2710
  callbackUrl,
2333
- issuer
2711
+ issuer,
2712
+ authHandler,
2713
+ signInResolver,
2714
+ tokenIssuer,
2715
+ catalogIdentityClient,
2716
+ logger
2334
2717
  });
2335
2718
  return OAuthAdapter.fromConfig(globalConfig, provider, {
2336
2719
  disableRefresh: false,
@@ -2340,362 +2723,222 @@ const createOneLoginProvider = (_options) => {
2340
2723
  });
2341
2724
  };
2342
2725
 
2343
- const factories = {
2344
- google: createGoogleProvider(),
2345
- github: createGithubProvider(),
2346
- gitlab: createGitlabProvider(),
2347
- saml: createSamlProvider(),
2348
- okta: createOktaProvider(),
2349
- auth0: createAuth0Provider(),
2350
- microsoft: createMicrosoftProvider(),
2351
- oauth2: createOAuth2Provider(),
2352
- oidc: createOidcProvider(),
2353
- onelogin: createOneLoginProvider(),
2354
- awsalb: createAwsAlbProvider(),
2355
- bitbucket: createBitbucketProvider(),
2356
- atlassian: createAtlassianProvider()
2357
- };
2358
-
2359
- function createOidcRouter(options) {
2360
- const { baseUrl, tokenIssuer } = options;
2361
- const router = Router__default["default"]();
2362
- const config = {
2363
- issuer: baseUrl,
2364
- token_endpoint: `${baseUrl}/v1/token`,
2365
- userinfo_endpoint: `${baseUrl}/v1/userinfo`,
2366
- jwks_uri: `${baseUrl}/.well-known/jwks.json`,
2367
- response_types_supported: ["id_token"],
2368
- subject_types_supported: ["public"],
2369
- id_token_signing_alg_values_supported: ["RS256"],
2370
- scopes_supported: ["openid"],
2371
- token_endpoint_auth_methods_supported: [],
2372
- claims_supported: ["sub"],
2373
- grant_types_supported: []
2374
- };
2375
- router.get("/.well-known/openid-configuration", (_req, res) => {
2376
- res.json(config);
2377
- });
2378
- router.get("/.well-known/jwks.json", async (_req, res) => {
2379
- const { keys } = await tokenIssuer.listPublicKeys();
2380
- res.json({ keys });
2381
- });
2382
- router.get("/v1/token", (_req, res) => {
2383
- res.status(501).send("Not Implemented");
2384
- });
2385
- router.get("/v1/userinfo", (_req, res) => {
2386
- res.status(501).send("Not Implemented");
2387
- });
2388
- return router;
2389
- }
2390
-
2391
- const CLOCK_MARGIN_S = 10;
2392
- class IdentityClient {
2393
- constructor(options) {
2394
- this.discovery = options.discovery;
2395
- this.issuer = options.issuer;
2396
- this.keyStore = new jose.JWKS.KeyStore();
2397
- this.keyStoreUpdated = 0;
2398
- }
2399
- async authenticate(token) {
2400
- var _a;
2401
- if (!token) {
2402
- throw new errors.AuthenticationError("No token specified");
2403
- }
2404
- const key = await this.getKey(token);
2405
- if (!key) {
2406
- throw new errors.AuthenticationError("No signing key matching token found");
2407
- }
2408
- const decoded = jose.JWT.IdToken.verify(token, key, {
2409
- algorithms: ["ES256"],
2410
- audience: "backstage",
2411
- issuer: this.issuer
2412
- });
2413
- if (!decoded.sub) {
2414
- throw new errors.AuthenticationError("No user sub found in token");
2415
- }
2416
- const user = {
2417
- id: decoded.sub,
2418
- token,
2419
- identity: {
2420
- type: "user",
2421
- userEntityRef: decoded.sub,
2422
- ownershipEntityRefs: (_a = decoded.ent) != null ? _a : []
2423
- }
2424
- };
2425
- return user;
2426
- }
2427
- static getBearerToken(authorizationHeader) {
2428
- if (typeof authorizationHeader !== "string") {
2429
- return void 0;
2430
- }
2431
- const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
2432
- return matches == null ? void 0 : matches[1];
2433
- }
2434
- async getKey(rawJwtToken) {
2435
- const { header, payload } = jose.JWT.decode(rawJwtToken, {
2436
- complete: true
2437
- });
2438
- const keyStoreHasKey = !!this.keyStore.get({ kid: header.kid });
2439
- const issuedAfterLastRefresh = (payload == null ? void 0 : payload.iat) && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
2440
- if (!keyStoreHasKey && issuedAfterLastRefresh) {
2441
- await this.refreshKeyStore();
2442
- }
2443
- return this.keyStore.get({ kid: header.kid });
2444
- }
2445
- async listPublicKeys() {
2446
- const url = `${await this.discovery.getBaseUrl("auth")}/.well-known/jwks.json`;
2447
- const response = await fetch__default["default"](url);
2448
- if (!response.ok) {
2449
- const payload = await response.text();
2450
- const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
2451
- throw new Error(message);
2452
- }
2453
- const publicKeys = await response.json();
2454
- return publicKeys;
2455
- }
2456
- async refreshKeyStore() {
2457
- const now = Date.now() / 1e3;
2458
- const publicKeys = await this.listPublicKeys();
2459
- this.keyStore = jose.JWKS.asKeyStore({
2460
- keys: publicKeys.keys.map((key) => key)
2461
- });
2462
- this.keyStoreUpdated = now;
2463
- }
2464
- }
2465
-
2466
- const MS_IN_S = 1e3;
2467
- class TokenFactory {
2726
+ class SamlAuthProvider {
2468
2727
  constructor(options) {
2469
- this.issuer = options.issuer;
2728
+ this.appUrl = options.appUrl;
2729
+ this.signInResolver = options.signInResolver;
2730
+ this.authHandler = options.authHandler;
2731
+ this.tokenIssuer = options.tokenIssuer;
2732
+ this.catalogIdentityClient = options.catalogIdentityClient;
2470
2733
  this.logger = options.logger;
2471
- this.keyStore = options.keyStore;
2472
- this.keyDurationSeconds = options.keyDurationSeconds;
2473
- }
2474
- async issueToken(params) {
2475
- const key = await this.getKey();
2476
- const iss = this.issuer;
2477
- const sub = params.claims.sub;
2478
- const ent = params.claims.ent;
2479
- const aud = "backstage";
2480
- const iat = Math.floor(Date.now() / MS_IN_S);
2481
- const exp = iat + this.keyDurationSeconds;
2482
- this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
2483
- return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
2484
- alg: key.alg,
2485
- kid: key.kid
2734
+ this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
2735
+ done(void 0, { fullProfile });
2486
2736
  });
2487
2737
  }
2488
- async listPublicKeys() {
2489
- const { items: keys } = await this.keyStore.listKeys();
2490
- const validKeys = [];
2491
- const expiredKeys = [];
2492
- for (const key of keys) {
2493
- const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
2494
- seconds: 3 * this.keyDurationSeconds
2495
- });
2496
- if (expireAt < luxon.DateTime.local()) {
2497
- expiredKeys.push(key);
2498
- } else {
2499
- validKeys.push(key);
2500
- }
2501
- }
2502
- if (expiredKeys.length > 0) {
2503
- const kids = expiredKeys.map(({ key }) => key.kid);
2504
- this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
2505
- this.keyStore.removeKeys(kids).catch((error) => {
2506
- this.logger.error(`Failed to remove expired keys, ${error}`);
2507
- });
2508
- }
2509
- return { keys: validKeys.map(({ key }) => key) };
2738
+ async start(req, res) {
2739
+ const { url } = await executeRedirectStrategy(req, this.strategy, {});
2740
+ res.redirect(url);
2510
2741
  }
2511
- async getKey() {
2512
- if (this.privateKeyPromise) {
2513
- if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
2514
- return this.privateKeyPromise;
2742
+ async frameHandler(req, res) {
2743
+ try {
2744
+ const context = {
2745
+ logger: this.logger,
2746
+ catalogIdentityClient: this.catalogIdentityClient,
2747
+ tokenIssuer: this.tokenIssuer
2748
+ };
2749
+ const { result } = await executeFrameHandlerStrategy(req, this.strategy);
2750
+ const { profile } = await this.authHandler(result, context);
2751
+ const response = {
2752
+ profile,
2753
+ providerInfo: {}
2754
+ };
2755
+ if (this.signInResolver) {
2756
+ const signInResponse = await this.signInResolver({
2757
+ result,
2758
+ profile
2759
+ }, context);
2760
+ response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
2515
2761
  }
2516
- this.logger.info(`Signing key has expired, generating new key`);
2517
- delete this.privateKeyPromise;
2518
- }
2519
- this.keyExpiry = luxon.DateTime.utc().plus({
2520
- seconds: this.keyDurationSeconds
2521
- }).toJSDate();
2522
- const promise = (async () => {
2523
- const key = await jose.JWK.generate("EC", "P-256", {
2524
- use: "sig",
2525
- kid: uuid.v4(),
2526
- alg: "ES256"
2762
+ return postMessageResponse(res, this.appUrl, {
2763
+ type: "authorization_response",
2764
+ response
2527
2765
  });
2528
- this.logger.info(`Created new signing key ${key.kid}`);
2529
- await this.keyStore.addKey(key.toJWK(false));
2530
- return key;
2531
- })();
2532
- this.privateKeyPromise = promise;
2533
- try {
2534
- await promise;
2535
2766
  } catch (error) {
2536
- this.logger.error(`Failed to generate new signing key, ${error}`);
2537
- delete this.keyExpiry;
2538
- delete this.privateKeyPromise;
2767
+ const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
2768
+ return postMessageResponse(res, this.appUrl, {
2769
+ type: "authorization_response",
2770
+ error: { name, message }
2771
+ });
2539
2772
  }
2540
- return promise;
2541
2773
  }
2542
- }
2543
-
2544
- const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
2545
- const TABLE = "signing_keys";
2546
- const parseDate = (date) => {
2547
- const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
2548
- if (!parsedDate.isValid) {
2549
- throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
2774
+ async logout(_req, res) {
2775
+ res.end();
2550
2776
  }
2551
- return parsedDate.toJSDate();
2777
+ }
2778
+ const samlDefaultSignInResolver = async (info, ctx) => {
2779
+ const id = info.result.fullProfile.nameID;
2780
+ const token = await ctx.tokenIssuer.issueToken({
2781
+ claims: { sub: id }
2782
+ });
2783
+ return { id, token };
2552
2784
  };
2553
- class DatabaseKeyStore {
2554
- static async create(options) {
2555
- const { database } = options;
2556
- await database.migrate.latest({
2557
- directory: migrationsDir
2785
+ const createSamlProvider = (options) => {
2786
+ return ({
2787
+ providerId,
2788
+ globalConfig,
2789
+ config,
2790
+ tokenIssuer,
2791
+ catalogApi,
2792
+ logger
2793
+ }) => {
2794
+ var _a, _b;
2795
+ const catalogIdentityClient = new CatalogIdentityClient({
2796
+ catalogApi,
2797
+ tokenIssuer
2558
2798
  });
2559
- return new DatabaseKeyStore(options);
2560
- }
2561
- constructor(options) {
2562
- this.database = options.database;
2563
- }
2564
- async addKey(key) {
2565
- await this.database(TABLE).insert({
2566
- kid: key.kid,
2567
- key: JSON.stringify(key)
2799
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
2800
+ profile: {
2801
+ email: fullProfile.email,
2802
+ displayName: fullProfile.displayName
2803
+ }
2568
2804
  });
2569
- }
2570
- async listKeys() {
2571
- const rows = await this.database(TABLE).select();
2572
- return {
2573
- items: rows.map((row) => ({
2574
- key: JSON.parse(row.key),
2575
- createdAt: parseDate(row.created_at)
2576
- }))
2577
- };
2578
- }
2579
- async removeKeys(kids) {
2580
- await this.database(TABLE).delete().whereIn("kid", kids);
2581
- }
2582
- }
2583
-
2584
- class MemoryKeyStore {
2585
- constructor() {
2586
- this.keys = /* @__PURE__ */ new Map();
2587
- }
2588
- async addKey(key) {
2589
- this.keys.set(key.kid, {
2590
- createdAt: luxon.DateTime.utc().toJSDate(),
2591
- key: JSON.stringify(key)
2805
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
2806
+ const signInResolver = (info) => signInResolverFn(info, {
2807
+ catalogIdentityClient,
2808
+ tokenIssuer,
2809
+ logger
2810
+ });
2811
+ return new SamlAuthProvider({
2812
+ callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
2813
+ entryPoint: config.getString("entryPoint"),
2814
+ logoutUrl: config.getOptionalString("logoutUrl"),
2815
+ audience: config.getOptionalString("audience"),
2816
+ issuer: config.getString("issuer"),
2817
+ cert: config.getString("cert"),
2818
+ privateKey: config.getOptionalString("privateKey"),
2819
+ authnContext: config.getOptionalStringArray("authnContext"),
2820
+ identifierFormat: config.getOptionalString("identifierFormat"),
2821
+ decryptionPvk: config.getOptionalString("decryptionPvk"),
2822
+ signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
2823
+ digestAlgorithm: config.getOptionalString("digestAlgorithm"),
2824
+ acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
2825
+ tokenIssuer,
2826
+ appUrl: globalConfig.appUrl,
2827
+ authHandler,
2828
+ signInResolver,
2829
+ logger,
2830
+ catalogIdentityClient
2592
2831
  });
2832
+ };
2833
+ };
2834
+
2835
+ const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
2836
+
2837
+ function createTokenValidator(audience, mockClient) {
2838
+ const client = mockClient != null ? mockClient : new googleAuthLibrary.OAuth2Client();
2839
+ return async function tokenValidator(token) {
2840
+ const response = await client.getIapPublicKeys();
2841
+ const ticket = await client.verifySignedJwtWithCertsAsync(token, response.pubkeys, audience, ["https://cloud.google.com/iap"]);
2842
+ const payload = ticket.getPayload();
2843
+ if (!payload) {
2844
+ throw new TypeError("Token had no payload");
2845
+ }
2846
+ return payload;
2847
+ };
2848
+ }
2849
+ async function parseRequestToken(jwtToken, tokenValidator) {
2850
+ if (typeof jwtToken !== "string" || !jwtToken) {
2851
+ throw new errors.AuthenticationError(`Missing Google IAP header: ${IAP_JWT_HEADER}`);
2593
2852
  }
2594
- async removeKeys(kids) {
2595
- for (const kid of kids) {
2596
- this.keys.delete(kid);
2597
- }
2853
+ let payload;
2854
+ try {
2855
+ payload = await tokenValidator(jwtToken);
2856
+ } catch (e) {
2857
+ throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
2598
2858
  }
2599
- async listKeys() {
2600
- return {
2601
- items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
2602
- createdAt,
2603
- key: JSON.parse(keyStr)
2604
- }))
2605
- };
2859
+ if (!payload.sub || !payload.email) {
2860
+ throw new errors.AuthenticationError("Google IAP token payload is missing sub and/or email claim");
2606
2861
  }
2862
+ return {
2863
+ iapToken: {
2864
+ ...payload,
2865
+ sub: payload.sub,
2866
+ email: payload.email
2867
+ }
2868
+ };
2607
2869
  }
2870
+ const defaultAuthHandler = async ({
2871
+ iapToken
2872
+ }) => ({ profile: { email: iapToken.email } });
2608
2873
 
2609
- const DEFAULT_TIMEOUT_MS = 1e4;
2610
- const DEFAULT_DOCUMENT_PATH = "sessions";
2611
- class FirestoreKeyStore {
2612
- constructor(database, path, timeout) {
2613
- this.database = database;
2614
- this.path = path;
2615
- this.timeout = timeout;
2616
- }
2617
- static async create(settings) {
2618
- const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
2619
- const database = new firestore.Firestore(firestoreSettings);
2620
- return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
2874
+ class GcpIapProvider {
2875
+ constructor(options) {
2876
+ this.authHandler = options.authHandler;
2877
+ this.signInResolver = options.signInResolver;
2878
+ this.tokenValidator = options.tokenValidator;
2879
+ this.tokenIssuer = options.tokenIssuer;
2880
+ this.catalogIdentityClient = options.catalogIdentityClient;
2881
+ this.logger = options.logger;
2621
2882
  }
2622
- static async verifyConnection(keyStore, logger) {
2623
- try {
2624
- await keyStore.verify();
2625
- } catch (error) {
2626
- if (process.env.NODE_ENV !== "development") {
2627
- throw new Error(`Failed to connect to database: ${error.message}`);
2628
- }
2629
- logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
2630
- }
2883
+ async start() {
2631
2884
  }
2632
- async addKey(key) {
2633
- await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
2634
- kid: key.kid,
2635
- key: JSON.stringify(key)
2636
- }));
2885
+ async frameHandler() {
2637
2886
  }
2638
- async listKeys() {
2639
- const keys = await this.withTimeout(this.database.collection(this.path).get());
2640
- return {
2641
- items: keys.docs.map((key) => ({
2642
- key: key.data(),
2643
- createdAt: key.createTime.toDate()
2644
- }))
2887
+ async refresh(req, res) {
2888
+ const result = await parseRequestToken(req.header(IAP_JWT_HEADER), this.tokenValidator);
2889
+ const context = {
2890
+ logger: this.logger,
2891
+ catalogIdentityClient: this.catalogIdentityClient,
2892
+ tokenIssuer: this.tokenIssuer
2645
2893
  };
2646
- }
2647
- async removeKeys(kids) {
2648
- for (const kid of kids) {
2649
- await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
2650
- }
2651
- }
2652
- async withTimeout(operation) {
2653
- const timer = new Promise((_, reject) => setTimeout(() => {
2654
- reject(new Error(`Operation timed out after ${this.timeout}ms`));
2655
- }, this.timeout));
2656
- return Promise.race([operation, timer]);
2657
- }
2658
- async verify() {
2659
- await this.withTimeout(this.database.collection(this.path).limit(1).get());
2894
+ const { profile } = await this.authHandler(result, context);
2895
+ const backstageIdentity = await this.signInResolver({ profile, result }, context);
2896
+ const response = {
2897
+ providerInfo: { iapToken: result.iapToken },
2898
+ profile,
2899
+ backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity)
2900
+ };
2901
+ res.json(response);
2660
2902
  }
2661
2903
  }
2662
-
2663
- class KeyStores {
2664
- static async fromConfig(config, options) {
2904
+ function createGcpIapProvider(options) {
2905
+ return ({ config, tokenIssuer, catalogApi, logger }) => {
2665
2906
  var _a;
2666
- const { logger, database } = options != null ? options : {};
2667
- const ks = config.getOptionalConfig("auth.keyStore");
2668
- const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
2669
- logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
2670
- if (provider === "database") {
2671
- if (!database) {
2672
- throw new Error("This KeyStore provider requires a database");
2673
- }
2674
- return await DatabaseKeyStore.create({
2675
- database: await database.getClient()
2676
- });
2677
- }
2678
- if (provider === "memory") {
2679
- return new MemoryKeyStore();
2680
- }
2681
- if (provider === "firestore") {
2682
- const settings = ks == null ? void 0 : ks.getConfig(provider);
2683
- const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
2684
- projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
2685
- keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
2686
- host: settings == null ? void 0 : settings.getOptionalString("host"),
2687
- port: settings == null ? void 0 : settings.getOptionalNumber("port"),
2688
- ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
2689
- path: settings == null ? void 0 : settings.getOptionalString("path"),
2690
- timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
2691
- }, (value) => value !== void 0));
2692
- await FirestoreKeyStore.verifyConnection(keyStore, logger);
2693
- return keyStore;
2694
- }
2695
- throw new Error(`Unknown KeyStore provider: ${provider}`);
2696
- }
2907
+ const audience = config.getString("audience");
2908
+ const authHandler = (_a = options.authHandler) != null ? _a : defaultAuthHandler;
2909
+ const signInResolver = options.signIn.resolver;
2910
+ const tokenValidator = createTokenValidator(audience);
2911
+ const catalogIdentityClient = new CatalogIdentityClient({
2912
+ catalogApi,
2913
+ tokenIssuer
2914
+ });
2915
+ return new GcpIapProvider({
2916
+ authHandler,
2917
+ signInResolver,
2918
+ tokenValidator,
2919
+ tokenIssuer,
2920
+ catalogIdentityClient,
2921
+ logger
2922
+ });
2923
+ };
2697
2924
  }
2698
2925
 
2926
+ const factories = {
2927
+ google: createGoogleProvider(),
2928
+ github: createGithubProvider(),
2929
+ gitlab: createGitlabProvider(),
2930
+ saml: createSamlProvider(),
2931
+ okta: createOktaProvider(),
2932
+ auth0: createAuth0Provider(),
2933
+ microsoft: createMicrosoftProvider(),
2934
+ oauth2: createOAuth2Provider(),
2935
+ oidc: createOidcProvider(),
2936
+ onelogin: createOneLoginProvider(),
2937
+ awsalb: createAwsAlbProvider(),
2938
+ bitbucket: createBitbucketProvider(),
2939
+ atlassian: createAtlassianProvider()
2940
+ };
2941
+
2699
2942
  async function createRouter(options) {
2700
2943
  const { logger, config, discovery, database, providerFactories } = options;
2701
2944
  const router = Router__default["default"]();
@@ -2713,7 +2956,13 @@ async function createRouter(options) {
2713
2956
  const secret = config.getOptionalString("auth.session.secret");
2714
2957
  if (secret) {
2715
2958
  router.use(cookieParser__default["default"](secret));
2716
- router.use(session__default["default"]({ secret, saveUninitialized: false, resave: false }));
2959
+ const enforceCookieSSL = authUrl.startsWith("https");
2960
+ router.use(session__default["default"]({
2961
+ secret,
2962
+ saveUninitialized: false,
2963
+ resave: false,
2964
+ cookie: { secure: enforceCookieSSL ? "auto" : false }
2965
+ }));
2717
2966
  router.use(passport__default["default"].initialize());
2718
2967
  router.use(passport__default["default"].session());
2719
2968
  } else {
@@ -2799,15 +3048,19 @@ exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
2799
3048
  exports.bitbucketUserIdSignInResolver = bitbucketUserIdSignInResolver;
2800
3049
  exports.bitbucketUsernameSignInResolver = bitbucketUsernameSignInResolver;
2801
3050
  exports.createAtlassianProvider = createAtlassianProvider;
3051
+ exports.createAuth0Provider = createAuth0Provider;
2802
3052
  exports.createAwsAlbProvider = createAwsAlbProvider;
2803
3053
  exports.createBitbucketProvider = createBitbucketProvider;
3054
+ exports.createGcpIapProvider = createGcpIapProvider;
2804
3055
  exports.createGithubProvider = createGithubProvider;
2805
3056
  exports.createGitlabProvider = createGitlabProvider;
2806
3057
  exports.createGoogleProvider = createGoogleProvider;
2807
3058
  exports.createMicrosoftProvider = createMicrosoftProvider;
2808
3059
  exports.createOAuth2Provider = createOAuth2Provider;
3060
+ exports.createOauth2ProxyProvider = createOauth2ProxyProvider;
2809
3061
  exports.createOidcProvider = createOidcProvider;
2810
3062
  exports.createOktaProvider = createOktaProvider;
3063
+ exports.createOneLoginProvider = createOneLoginProvider;
2811
3064
  exports.createOriginFilter = createOriginFilter;
2812
3065
  exports.createRouter = createRouter;
2813
3066
  exports.createSamlProvider = createSamlProvider;